5.学城项目 支付宝支付
生活随笔
收集整理的這篇文章主要介紹了
5.学城项目 支付宝支付
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
1. 支付寶API文檔
* 1. 官方文檔地址: https://opendocs.alipay.com/common/02fwvj/* 2. 支付寶API:六大接口 地址: https://docs.open.alipay.com/270/105900/* 3. 支付寶工作流程 地址: https://docs.open.alipay.com/270/105898/* 4. 支付寶8次異步通知機制(支付寶對我們服務器發送POST請求, 索要 success 7個字符)地址: https://docs.open.alipay.com/270/105902/2. 沙箱環境下測試支付
2.1 測試步驟
沙箱環境是協助開發者進行接口開發及主要功能聯調的模擬環境. * 1. 電腦網站支付API: https://docs.open.alipay.com/270/105900/* 2. 在沙箱環境下實名認證地址: https://openhome.alipay.com/platform/appDaily.htm?tab=info* 3. 完成RSA密鑰生成:https://docs.open.alipay.com/291/105971* 4. 在開發中心的沙箱應用下設置應用公鑰:填入生成的公鑰文件中的內容* 5. Python支付寶開源第三方框架:https://github.com/fzlee/alipay* 6. pip install python-alipay-sdk --upgrade* 7. 代碼的密鑰配置格式alipay_public_key.pem = """-----BEGIN PUBLIC KEY-----支付寶公鑰-----END PUBLIC KEY-----"""app_private_key.pem = """-----BEGIN RSA PRIVATE KEY-----用戶私鑰-----END RSA PRIVATE KEY-----"""* 8. 支付寶網關真實:https://openapi.alipay.com/gateway.do沙箱:https://openapi.alipaydev.com/gateway.do # 帶dev2.2 沙箱環境配置
* 1. 認證 https://developers.alipay.com/dev/workspace/register?from=http%3A%2F%2Fopenhome.alipay.com%2Fplatform%2FappDaily.html * 2. 配置沙箱應用1. 選擇網頁&移動應用2. 接口加簽方式選擇自定義密鑰3. 公鑰模式->設置并查看 * 3. 下載&安裝 支付寶密鑰生成器下載地址: https://gw.alipayobjects.com/os/bmw-prod/02b946e1-9faf-4394-8004-d241443c874e.exe * 4. 生成密鑰 * 5. 復制公鑰 * 6. 將應用公鑰填寫到到沙箱中 * 7. 生成支付寶公鑰 * 8. 沙箱賬戶 * 9. 下載支付寶客戶端沙箱版本 * 10. 手機端安裝支付寶沙箱版買家賬號 aijatb2766@sandbox.com登錄密碼 111111支付密碼 1111112.3 測試代碼
* 1. 安裝python-alipay-sdk模塊pip install python-alipay-sdk如果拋ssl相關錯誤, 代表缺失pyopenssl模塊pip install pyopenssl * 2. 測試代碼 (設置密鑰的時候不要有多余的空格) from alipay import AliPay# 應用程序私鑰 app_private_key_string = """-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAhe3/9BI+qAaoRprc1f3liRA9v9WYL/n9Xp9Cc+PPcbkfrI/pN7q/GOaaYf521wbCEXmZYW4qQVX4mP4i2S05FwRu2B1/7Ux4oREyM+0vazHrBCNyE7erqGV2+8GsLDJRnyQXApbN79FR40+chrlc5Dt29mGCtv1DKz1l/mnNJKj0srP55joxXfLmDwfeLcNcTEblJsxpgi1TKlnEG0b/0RtkFhGGOsUrClNiLxBK5iZJBUt+QX2P97MDHj8BIqLqKITBe+aSbwHcDzl587S9cq1142y41NVhEjlC20+2iHsESA82HQo8iH7i1A6BzHvRlvk7HoUqeYb8p91ZLI0d7QIDAQABAoIBAAuppQ9RA2nIYqD7XV25JWLhKi9pXz5WS60Qu02yOd9SWqLBSXLT7U4yzqDX8utYqE+zQhsM59sWrHZOMySsXntVpH1nXDuC3EJSaAfDkMyJ5UhP+eAjr2wTod/chqy2mQr9ro9IKJjIppPf2+aTf7ZUQ1DDPwnGVjIOv7H+7qFRf/XBUVyyW1j4uwFlN/ZporDa7B6lwqWgH9+Yvo7w3/Pmjo78z2BKZzPQJa2sq3WxolcphDImflycHzno8jIn+QnB3XmlCu7yxsx4HYXMQMKY8i3gzPejdOdWsjbAuxI3mfyyELN2W8ZCpaZKWfKEhpxR4DA56byZtESWN+s1HmECgYEA7WFD1vhO2gwWyiFBzyu9yHOTpmcH6mBJ30Km7mErPHD3yHOiaCv59Ba4XTD629073m3Q2z6EXpmv7Fwz+uezac12BCBeg7AU9WfBJPiSHBRvwR4qXbZzbU91tElWDFYlwCmjd3Sp5oTGWSpWU/BW3hkxx6KYIwnYUIDIhTChCQUCgYEAkG9j4pp/XisOCUM3cQmTaUzDWZfOILDQUjrWCnLEHfPJPrX5qjtkSkZ8Z6Q4GjGNVg5m4i98VRGkt3rY0wsnsuCDFrws7TzlvDVP9fo0DgTalO4JCQ4gE9fGZypbjRYtbHobTEsGnGmeE3eRrlY5cfNfcwfrr1vKceo7slJzNckCgYAd+8kr4BVlqV0/js/XMTk5lo+x1xXC3wK1tp+LQK7LZaGGqkR7UAK0eCI1czhciSdEwy48YzspD9SO0F6odJfO52revo/xpk4faUmWN+eMsHAlPoAvchpGVmERsqmxyTffe+Lv9cZ4HZFINfbNh3ARgbEt/DWnR1kRYhLx7+CHWQKBgQCNWGTwkm9IsWu4Bs6P0WYwK04VNGklNsN3ZVqnuO5RvYxY0W71d8/KnDYMmvnIMGv3JnrqqLvM6EpAwHjF92mvNOU0b4yr0eelCqsoteURPxDFpDi1YtxjbssblKkpZeWn/csPG3DpyrZGqMGpUXpAGIJ1KPAtmO+CEU7AUM2seQKBgQDmPWXLUXx6FKRVUYLgALEY+PkIc3feRw1gdhSa7Q8/sQw8jugD92YnZ9gdBkZxIlPHqiMpvba+adbFTcTIPx6Myoy8X8mfWFTHU0su+tiwiUXGWkxAvYU/8ebSABs6HRgFGkbJEDU7TqPOvDCLFjAFdTQJ/LKGwbI+E7XfR0f4Sw== -----END RSA PRIVATE KEY-----"""# 支付寶公鑰 alipay_public_key_string = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtfLBZtfSKJ3mwThLuRnLO1eEh5jf2fXDlC06VLH/lmFFJrZBfkJqzE9T41k5JoGpluI6r03vo6Vx29UYOP0bf531UUr9Bpg2TLBwmUVQYoY15plsUjKkqTqDYq55m3cYJpn0TBROMyurbGs4i0TcNq35zjC2wbl0DWmxmOhWyg3NhUu9uqWUJGbfxTuowKQxaK/3XDoOLx+mQCytz0JvNevUhBhOu6eZLCh3hNOswu8gMf3a5kCReya24uXOd26SKd5HYlwFHk4ndSSjwIYulQs9hnlSbkJn+zykjc6qdDQLMWuqkSKlb5Snu7TCh5FNUO4KsFqk2bEXgysJy23lhwIDAQAB -----END PUBLIC KEY-----""" alipay = AliPay(# 應用idappid="2021000120612450",# 默認回調app_notify_url=None,# 應用私鑰字符串app_private_key_string=app_private_key_string,# 支付寶公鑰字符串alipay_public_key_string=alipay_public_key_string,# 加密方式sign_type="RSA2", # RSA or RSA2# 開啟調試(上線關閉)debug=True )# 網關地址(上線之后需要修改) alipay_url = 'https://openapi.alipaydev.com/gateway.do?' # 生成對象(交易頁面) order_string = alipay.api_alipay_trade_page_pay(# 訂單號唯一標識out_trade_no="202206041111",# 價格total_amount=9.9,# 商品名稱subject='<<腎寶>>男人要腎好,就要喝腎寶。喝了之后,比劉翔快,比姚明高。一瓶提神醒腦,兩瓶永不疲勞,三瓶長生不老。噢耶...',# 支付成功 get同步回調, 前臺展示的頁面, # return_url="",# 支付成功 post異步回調, 向后端發送請求 修改訂單狀態(以支付)# notify_url="" )# 生成支付鏈接 (網關 + 交易頁面) print(alipay_url + order_string) * 3. 運行程序, 得到一個商品支付地址: https://openapi.alipaydev.com/gateway.do?app_id=2021000120612450&biz_content=%7B%22subject%22%3A%22%5Cu7537%5Cu4eba%5Cu8981%5Cu80be%5Cu597d%5Cuff0c%5Cu5c31%5Cu8981%5Cu559d%5Cu80be%5Cu5b9d%5Cu3002%5Cu559d%5Cu4e86%5Cu4e4b%5Cu540e%5Cuff0c%5Cu6bd4%5Cu5218%5Cu7fd4%5Cu5feb%5Cuff0c%5Cu6bd4%5Cu59da%5Cu660e%5Cu9ad8%5Cu3002%5Cu4e00%5Cu74f6%5Cu63d0%5Cu795e%5Cu9192%5Cu8111%5Cuff0c%5Cu4e24%5Cu74f6%5Cu6c38%5Cu4e0d%5Cu75b2%5Cu52b3%5Cuff0c%5Cu4e09%5Cu74f6%5Cu957f%5Cu751f%5Cu4e0d%5Cu8001%5Cu3002%5Cu5662%5Cu8036...%22%2C%22out_trade_no%22%3A%22%5Cu80be%5Cu5b9d%22%2C%22total_amount%22%3A9.9%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay¬ify_url=https%3A%2F%2Fwww.luffycity.com%2Ffree-course&return_url=https%3A%2F%2Fwww.luffycity.com%2Ffree-course&sign_type=RSA2×tamp=2022-06-03+23%3A26%3A25&version=1.0&sign=BttMCJX7M6PGFNq1ldnaxR0HM1zLcVRbCeC7xmM3NDceOvs2U8tnRHmZb1gKlbozH2T8arQfPHrtJQ4dkjpptQHJMGWD6AU0cQzSi%2F4dI3m5GKEK4Y96w9OC1UNTjp80TXwZ1OlPCTzpHB4SAkjHeRSbxPXGJpvsvt7TjKOhEIbZ02xNAfawLa%2F2ptv6FXtplSb1ckmo1aV2k%2Fa8rQxRUVA52eeM9XNoH7GpoUiDAi47NZalgm1bcsrvrHfDizR83AU39hddA8EkqxFFAlFAOpVCRH6ZWSFgxJfTap4b6KkViYkaee74eELMgvG9qcuV9b04DB3JdFfbctl1VXyqrQ%3D%3D * 4. 使用瀏覽器打開地址(如果瀏覽器打開的網站太多, 可能會提示釣魚網站之類的提示.) * 5. 使用支付寶沙箱版本支付 * 6. 沙箱賬戶中查看余額3. 二次封裝支付寶
在項目lib目錄下重新封裝支付寶 配置可以寫在iPay下的settings.py文件或項目的dev.py配置文件中. lib├── iPay # aliapy二次封裝包│ ├── __init__.py # 包文件│ ├── pem # 公鑰私鑰文件夾│ │ ├── alipay_public_key.pem # 支付寶公鑰文件│ │ ├── app_private_key.pem # 應用私鑰文件│ ├── pay.py # 支付文件└── └── settings.py | dev.py # 應用配置 * 1. 在項目lib目錄下新建iPay目錄, 再按照目錄結構創建其他文件和目錄. * 2. 在app_private_key.pem中寫入支付寶公鑰 -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAhe3/9BI+qAaoRprc1f3liRA9v9WYL/n9Xp9Cc+PPcbkfrI/pN7q/GOaaYf521wbCEXmZYW4qQVX4mP4i2S05FwRu2B1/7Ux4oREyM+0vazHrBCNyE7erqGV2+8GsLDJRnyQXApbN79FR40+chrlc5Dt29mGCtv1DKz1l/mnNJKj0srP55joxXfLmDwfeLcNcTEblJsxpgi1TKlnEG0b/0RtkFhGGOsUrClNiLxBK5iZJBUt+QX2P97MDHj8BIqLqKITBe+aSbwHcDzl587S9cq1142y41NVhEjlC20+2iHsESA82HQo8iH7i1A6BzHvRlvk7HoUqeYb8p91ZLI0d7QIDAQABAoIBAAuppQ9RA2nIYqD7XV25JWLhKi9pXz5WS60Qu02yOd9SWqLBSXLT7U4yzqDX8utYqE+zQhsM59sWrHZOMySsXntVpH1nXDuC3EJSaAfDkMyJ5UhP+eAjr2wTod/chqy2mQr9ro9IKJjIppPf2+aTf7ZUQ1DDPwnGVjIOv7H+7qFRf/XBUVyyW1j4uwFlN/ZporDa7B6lwqWgH9+Yvo7w3/Pmjo78z2BKZzPQJa2sq3WxolcphDImflycHzno8jIn+QnB3XmlCu7yxsx4HYXMQMKY8i3gzPejdOdWsjbAuxI3mfyyELN2W8ZCpaZKWfKEhpxR4DA56byZtESWN+s1HmECgYEA7WFD1vhO2gwWyiFBzyu9yHOTpmcH6mBJ30Km7mErPHD3yHOiaCv59Ba4XTD629073m3Q2z6EXpmv7Fwz+uezac12BCBeg7AU9WfBJPiSHBRvwR4qXbZzbU91tElWDFYlwCmjd3Sp5oTGWSpWU/BW3hkxx6KYIwnYUIDIhTChCQUCgYEAkG9j4pp/XisOCUM3cQmTaUzDWZfOILDQUjrWCnLEHfPJPrX5qjtkSkZ8Z6Q4GjGNVg5m4i98VRGkt3rY0wsnsuCDFrws7TzlvDVP9fo0DgTalO4JCQ4gE9fGZypbjRYtbHobTEsGnGmeE3eRrlY5cfNfcwfrr1vKceo7slJzNckCgYAd+8kr4BVlqV0/js/XMTk5lo+x1xXC3wK1tp+LQK7LZaGGqkR7UAK0eCI1czhciSdEwy48YzspD9SO0F6odJfO52revo/xpk4faUmWN+eMsHAlPoAvchpGVmERsqmxyTffe+Lv9cZ4HZFINfbNh3ARgbEt/DWnR1kRYhLx7+CHWQKBgQCNWGTwkm9IsWu4Bs6P0WYwK04VNGklNsN3ZVqnuO5RvYxY0W71d8/KnDYMmvnIMGv3JnrqqLvM6EpAwHjF92mvNOU0b4yr0eelCqsoteURPxDFpDi1YtxjbssblKkpZeWn/csPG3DpyrZGqMGpUXpAGIJ1KPAtmO+CEU7AUM2seQKBgQDmPWXLUXx6FKRVUYLgALEY+PkIc3feRw1gdhSa7Q8/sQw8jugD92YnZ9gdBkZxIlPHqiMpvba+adbFTcTIPx6Myoy8X8mfWFTHU0su+tiwiUXGWkxAvYU/8ebSABs6HRgFGkbJEDU7TqPOvDCLFjAFdTQJ/LKGwbI+E7XfR0f4Sw== -----END RSA PRIVATE KEY----- * 3. 在alipay_public_key.pem文件中寫入應用程序私鑰 -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtfLBZtfSKJ3mwThLuRnLO1eEh5jf2fXDlC06VLH/lmFFJrZBfkJqzE9T41k5JoGpluI6r03vo6Vx29UYOP0bf531UUr9Bpg2TLBwmUVQYoY15plsUjKkqTqDYq55m3cYJpn0TBROMyurbGs4i0TcNq35zjC2wbl0DWmxmOhWyg3NhUu9uqWUJGbfxTuowKQxaK/3XDoOLx+mQCytz0JvNevUhBhOu6eZLCh3hNOswu8gMf3a5kCReya24uXOd26SKd5HYlwFHk4ndSSjwIYulQs9hnlSbkJn+zykjc6qdDQLMWuqkSKlb5Snu7TCh5FNUO4KsFqk2bEXgysJy23lhwIDAQAB -----END PUBLIC KEY----- * 4. settings.py 配置文件中填寫參數 import os# 獲取到iPay的路徑 BASE_DIR = os.path.dirname(__file__) print(BASE_DIR) # 得到iPay下的pem目錄路徑 PEM_DIR = os.path.join(BASE_DIR, 'pem') # 應用程序私鑰地址 APP_PRIVATE_KEY_URL = os.path.join(PEM_DIR, 'app_private_key.pem') # 支付寶公鑰地址 ALIPAY_PUBLIC_KEY_URL = os.path.join(PEM_DIR, 'alipay_public_key.pem')# 應用id APPID = '2021000120612450'# 默認回調 APP_NOTIFY_URL = None# 應用私鑰字符串 with open(APP_PRIVATE_KEY_URL, mode='r', encoding='utf8') as rf1:APP_PRIVATE_KEY_STRING = rf1.read()# 支付寶公鑰字符串 with open(ALIPAY_PUBLIC_KEY_URL, mode='r', encoding='utf8') as rf2:ALIPAY_PUBLIC_KEY_STRING = rf2.read()# 加密方式 SIGN_TYPE = "RSA2"# BUG模式 DEBUG = True# 沙箱網關 ( DEBUG = True ) dev = 'https://openapi.alipaydev.com/gateway.do?'# 真實網關 ( DEBUG = False ) master = 'https://openapi.alipay.com/gateway.do?'# 網關 ALIPAY_URL = dev if DEBUG else master * 5. 在pay.py生成alipay支付對象 # 導入配置參數 from .settings import *# 導入AliPay模塊 from alipay import AliPay# 生成alipay對象 alipay = AliPay(# 應用idappid=APPID,# 默認回調app_notify_url=APP_NOTIFY_URL,# 應用私鑰字符串app_private_key_string=APP_PRIVATE_KEY_STRING,# 支付寶公鑰字符串alipay_public_key_string=ALIPAY_PUBLIC_KEY_STRING,# 加密方式sign_type=SIGN_TYPE,# 調試開關(上線設置False)debug=False,) * 6. __init__.py 中導入alipay對象 與 網關在導入iPay時會自動觸發__init__.py,則導入alipay對象 與 網關 # 導入alipay對象 與 網關 from .pay import alipay from .settings import ALIPAY_URL""" 在導入iPay時則導入alipay對象 與 網關 from luffy/lib import iPay iPay.alipay ... iPay.ALIPAY_URL ... """4. 訂單接口
4.1 創建app
* 1. 新建訂單app cd luffy/apps/ python ../../manage.py startapp order * 2. 注冊app INSTALLED_APPS = [...'order' * 3. 總路由配置 path('order/', include('order.urls')), * 4. 子路由 先在order目錄下新建urls.py from django.urls import re_path, includefrom . import views # 導入SimpleRouter from rest_framework.routers import SimpleRouter# 生成對象 router = SimpleRouter() # 注冊路由 router.register('pay', views.PayView, 'pay')# 路由列表 urlpatterns = [re_path('', include(router.urls)), ]4.2 表設置
1. 訂單表 2. 訂單表與課程表的第三張表 from django.db import models# 導入用戶表 from user.models import User # 導入課程表 from course.models import Course# 訂單表 class Order(models.Model):"""用戶表(一) 對 (多)訂單表一個用戶可以有多個訂單, 一個訂單只能有一個用戶. 一對多關系, 關聯字段寫在多的一方."""# 支付狀態status_choices = ((0, '未支付'),(1, '已支付'),(2, '以超時'),(3, '超時取消'))# 支付狀態pay_choices = ((0, '支付寶'),(1, '微信'))# 訂單標簽subject = models.CharField(max_length=150, verbose_name='訂單標題')# 訂單總價total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='訂單總價', default=0)# 訂單號 唯一out_trade_no = models.CharField(max_length=64, verbose_name='訂單號', unique=True)# 訂單狀態order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name='訂單狀態')# 支付方式pay_type = models.SmallIntegerField(choices=pay_choices, default=0, verbose_name='支付方式')# 支付時間pay_time = models.DateTimeField(null=True, verbose_name='支付時間')# 外鍵user = models.ForeignKey(to=User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False,verbose_name='下單用戶')# 創建時間created_time = models.DateTimeField(auto_now_add=True, verbose_name='創建時間')# 最后更新時間updated_time = models.DateTimeField(auto_now=True, verbose_name='最后更新時間')# 定義元類class Meta:# 表名db_table = 'luffy_order'# 定義一個變量verbose_name = '訂單記錄'# 后臺展示的表名verbose_name_plural = verbose_name# 打印對象時展示商品的名字與價格def __str__(self):return f'{self.subject} - ¥:{self.total_amount}'# 訂單詳情 class OrderDetail(models.Model):"""訂單表(多) 對 (多)課程表一個訂單可以有多個課程, 一個課程可以在對個訂單中第三張表外鍵關聯訂單表, 關聯課程表, 創建額外的字段"""# 外鍵 關聯訂單表order = models.ForeignKey(to=Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False,verbose_name='訂單外鍵')# 外鍵 關聯關聯課程表course = models.ForeignKey(to=Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False,verbose_name='課程外鍵')# 原價price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name='課程原價')# 實價real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name='課程實價')# 定義元類class Meta:# 表名db_table = 'luffy_order_detail'# 定義一個變量verbose_name = '訂單詳情'# 定向詳情verbose_name_plural = verbose_name# 打印對象時展示 課程名字:課程價格def __str__(self):try:return f'{self.course.name}的訂單: {self.order.out_trade_no}'# 沒有錄數據的時候不會至于報錯except:return super().__str__() 數據庫遷移: python manage.py makemigrations python manage.py migrate # 修改order目錄下的admin.py 文件將表注冊到后臺 import xadminfrom . import models # 將訂單表與訂單詳情表上條件到后臺管理 xadmin.site.register(models.Order) xadmin.site.register(models.OrderDetail) # 后臺展示中文菜單 # 修改order下的__init__.py default_app_config = "order.apps.OrderConfig"# 修改order下的apps.py from django.apps import AppConfigclass OrderConfig(AppConfig):name = 'order'verbose_name = '訂單'4.3 接口
支付接口分析 1. 支付接口生成訂單, 生成支付連接, 返回支付鏈接2. 支付成功時: 支付寶異步回調的post接口驗證簽名, 修改訂單狀態3. 支付成功時: 支付寶同步get回調 (支付成功后展示的頁面)vue頁面創建時 生命周期鉤子函數發送請求去后端查詢訂單狀態(可有可無) 1. 訂單總價校驗從數據庫中獲取對應的課程對象, 課程的價格取出來累加, 再與前端傳遞的值比較是否相等. 2. 生成訂單號使用一個uuid生成一個隨機字符串 3. 支付鏈接生成使用自己封裝的支付寶模塊生成支付連接, 將支付鏈接存放在序列化對象的context屬性中. 4. request.user 中獲取用戶對象視圖類中重寫create方法, 為context參數設置值為request, 序列化中取出用戶對象 5. 入庫(兩張表)的信息準備將用戶對象保存到attrs字典中 6. 重寫序列化的create方法將課程從validated_data字典中pop出來...先創建支付表的數據, 在遍歷創建支付詳情表的數據 * 1. 模型序列化器 # 導入模型序列化 from rest_framework import serializers# 導入模型層 from . import models# 導入異常模塊 from rest_framework.exceptions import ValidationErrorclass OrderModelSerializer(serializers.ModelSerializer):course_obj = models.Course.objects.all()"""PrimaryKeyRelatedField 將 {'course': [課程id1, 課程id2, ...]} 轉為 {'course': [課程1_obj, 課程2_obj, ...]}queryset參數對于轉換表的queryset對象, 提交的值是一個列表, 設置many=True, 只寫模式Course表與 Order可以沒有關系,只要設置queryset的參數值是一張表的數據即可 通過id獲取到表對應的數據對象 """course = serializers.PrimaryKeyRelatedField(queryset=course_obj, many=True, write_only=True)class Meta:model = models.Order# 轉換的字段 訂單標簽 價格 支付方式 購買的課程fields = ['subject', 'total_amount', 'pay_type', 'course']# (設置了默認值, 不會提示價格 支付方式字段必填, 使用require提示字段必須填寫)extra_kwargs = {'total_amount': {'required': True},'pay_type': {'required': True},}# 全局校驗def validate(self, attrs):# 1. 校驗價格, 檢驗不成功直接拋出異常, 校驗成功返回價格total_amount = self._check_price(attrs)# 2. 生成訂單號out_trade_no = self._gen_out_trade_no()# 3. 生成支付鏈接并保存到context參數中self._gen_pay_url(attrs, out_trade_no, total_amount)# 4. 獲取用戶對象 在視圖類中重寫create方法, 在生成模型序列化對象時將request通過context參數傳遞user_obj = self._get_user()# 5. 寫入數據庫前準備self._before_create(attrs, user_obj, out_trade_no)return attrs# 價格校驗@staticmethoddef _check_price(attrs):# 獲取提交的價格total_amount = attrs.get('total_amount')# 獲取課程對象course_list = attrs.get('course')# 計算數據庫中的價格total_price = 0for course_obj in course_list:total_price += course_obj.price# 判斷值是都相等的if total_amount != total_price:# 價格不一致, 直接拋出異常raise ValidationError('價格不合法')return total_amount# 生成訂單號@staticmethoddef _gen_out_trade_no():# 訂單號是一個唯一字符串, 使用uuid即可from uuid import uuid4# uuid的類型值的格式 bce5eed9-8985-42ab-b37a-51bf8909c1c5 類型是 <class 'uuid.UUID'># 將類類型轉為str類型, 再將-去除 bce5eed9898542abb37a51bf8909c1c5return str(uuid4()).replace('-', '')# 生成支付鏈接def _gen_pay_url(self, attrs, out_trade_no, total_amount):# 導入支付模塊from luffy.lib import iPay# 獲取商品名稱(前端傳入)subject = attrs.get('subject')# 導入回調地址from django.conf import settings# 網關地址alipay_url = iPay.ALIPAY_URL# total_amount是Decimal類型, 支付寶無法識別, 轉為float類型即可print(total_amount, type(total_amount)) # 138.00 <class 'decimal.Decimal'>total_amount = float(total_amount)# 生成對象order_string = iPay.alipay.api_alipay_trade_page_pay(# 訂單號唯一標識out_trade_no=out_trade_no,# 價格total_amount=total_amount,# 商品名稱subject=subject,# 支付成功 get同步回調, 前臺展示的頁面,return_url=settings.NOTIFY_URL,# 支付成功 post異步回調, 向后端發送請求 修改訂單狀態(以支付)notify_url=settings.RETURN_URL)# 生成支付地址pay_url = alipay_url + order_string# 將支付鏈接保存到context中self.context['pay_url'] = pay_url# 獲取用戶def _get_user(self):# 從context中獲取request對象request = self.context.get('request')# 放回user對象(登入成功之后request.user會有用戶的數據對象)return request.user# 寫入數據前準備@staticmethoddef _before_create(attrs, user_obj, out_trade_no):# 將用戶對象寫入到序列化字典中attrs['user'] = user_obj# 將訂單號寫入到序列化字典中attrs['out_trade_no'] = out_trade_nodef create(self, validated_data):# 將課程列表虛擬字段pop掉course_list = validated_data.pop('course')# 支付表數據寫入(需要的數據, 'subject', 'total_amount', 'course', 'user')order_obj = models.Order.objects.create(**validated_data)# 寫入支付詳情表, 需要的數據(order, course, price, real_price)for course_obj in course_list:models.OrderDetail.objects.create(order=order_obj, course=course_obj, price=course_obj.price,real_price=course_obj.price)# 將支付對象返回, 返回了也沒用到return order_obj # 支付寶回調地地址 # 后臺URL BASE_URL = 'http://127.0.0.1:8000' # 前臺URL LUFFY_URL = 'http://127.0.0.1:8080'# 后臺異步回調地址 RETURN_URL = BASE_URL + '/order/success/' # 前臺同步回調地址(沒有/結尾) NOTIFY_URL = LUFFY_URL + '/pay/success' * 2. 視圖類單獨使用rest_framework_jwt.authentication.JSONWebTokenAuthentication的話所有游客都可以訪問, request.user 是AnonymousUser 匿名用戶, 使用rest_framework內置權限類, 對登入用戶進行校驗, 匿名用戶禁止訪問! from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import CreateModelMixin from . import models from .serializer import OrderModelSerializer from rest_framework.response import Response from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated# 生成支付訂單 class PayView(GenericViewSet, CreateModelMixin):# 登入認證類 + 限制類 現在限制只有登入用戶才能使用authentication_classes = [JSONWebTokenAuthentication] # 沒有用戶登入返回permission_classes = [IsAuthenticated]# 使用的表數據queryset = models.Order.objects.all()# 使用的模型序列化類serializer_class = OrderModelSerializer# 重寫視圖類的 create方法 (復制CreateModelMixin的create方法進行修改)def create(self, request, *args, **kwargs):# 生成序列化器時, 將request對象創建給模型序列化器 通過context參數serializer = self.get_serializer(data=request.data, context=request)# 校驗數據serializer.is_valid(raise_exception=True)# 保存數據self.perform_create(serializer)headers = self.get_success_headers(serializer.data)return Response(serializer.context.get('pay_url'))4.4 接口測試
* 1. 接口地址: http://127.0.0.1:8000/order/pay/ * 2. 需要先登入(post請求)登入地址: http://127.0.0.1:8000/user/login/?username=root&password=zxc123456 * 3. 攜帶token訪問支付接口 key: Authorization value: jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InJvb3QiLCJleHAiOjE2NTUyOTg4NTYsImVtYWlsIjoiMTM2QHFxLmNvbSJ9.7vHsFCMCM-8_Uc5-bOvEFMvUf4d5fBtCGayQG53mb2c * token前面攜帶 jwt空格 * 4. 價格不合法測試三門課程都現在, 價格是138, 現在隨便填寫一個錯誤的價格 * 5. 價格正常測試, 返回支付地址4.5 前臺點擊購買事件
為點擊購買綁定事件, 免費課程頁面, 頁面詳情, 搜索頁面, 三個位置. 只有等有用戶才能購買: 登入成功之后 this.$cookies.set('token', response.data.token) 將token保存到了scookies中 先從cokkies中獲取token, 如果有值則可以發送請求, 如果沒有值則提示用戶登入.拿到支付地址之后跳轉到支付頁面(新開設頁面) window.open(url) 簡寫 open(url) 在當前頁面打開網址 open(url, '_self') <span class="buy-now" @click="buy_now(course)">立即購買</span> // 購買課程 buy_now(course) {// 從cookies中獲取tokenlet token = this.$cookies.get('token')// 判斷是否為登入狀態if (!token) {// 提示用戶登入this.$message({message: '請先登入!'})return false}// 用戶已經登入向后臺發送請求this.$axios({method: 'post', url: this.$settings.base_url + '/order/pay/',headers: {Authorization: 'jwt ' + token}, data: {// 課程的名字作為商品的標題"subject": course.name,// 課程的價格"total_amount": course.price,// 支付方式"pay_type": 1,// 課程的id"course": [course.id]}}).then(response => {// 訪問成功獲取到支付鏈接 直接從response.data中獲取數據(后端直接返回鏈接, 不是返回request.data)// 視圖序列化器中報錯會 需要從response.data.dat中獲取錯誤信息// console.log(response.data)// 跳轉到支付地址if (response.data.data) {this.$message({message: response.data.data // 異常新 例如 { "total_amount": [ "該字段是必填項。" ] }})return false}// 返回的不是錯誤信息那么就是支付鏈接let pay_url = response.data// console.log(pay_url)// 跳轉到支付鏈接open(pay_url, '_self')}).catch(error => {this.$message({message: '未知錯誤, 請聯系管理員!'})}) }4.6 同步回調頁面
兩部分: 1. 分析支付寶回調參數展示訂單的支付情況 2. 向后端get請求, 查詢后端是否修改訂單狀態 * 1. 新建支付成功展示的頁面 PayCourse.vue <template><div class="pay-success"><!--如果是單獨的頁面,就沒必要展示導航欄(帶有登錄的用戶)--><Header/><div class="main"><div class="title"><div class="success-tips"><p class="tips">您已成功購買 1 門課程!</p></div></div><div class="order-info"><p class="info"><b>訂單號:</b><span>{{ result.out_trade_no }}</span></p><p class="info"><b>交易號:</b><span>{{ result.trade_no }}</span></p><p class="info"><b>付款時間:</b><span><span>{{ result.timestamp }}</span></span></p></div><div class="study"><span>立即學習</span></div></div></div> </template><script> import Header from "@/components/Head"export default {name: "PayCourse",data() {return {result: {},};},created() {// location.search 獲取 url后拼接的參數:?及后面的所有參數 => ?a=1&b=2// console.log(location.search);// 解析支付寶回調的url參數let params = location.search.substring(1); // 去除? 保留 a=1&b=2// 按&切分得到一個元組, 要有是一個空元組let items = params.length ? params.split('&') : [];// console.log(items)/*0: "charset=utf-8"1: "out_trade_no=1c6bd7ee950842249a7cb0b6956224d2"...*/// 將每一項逐個添加到args對象中for (let i = 0; i < items.length; i++) { // 第一次循環a=1,第二次b=2// 按=切分得到 k = vlet k_v = items[i].split('='); // ['a', '1']// 列表元素少與2直接忽略, 有鍵缺值的情況if (k_v.length >= 2) {// url編碼反解 解碼操作,因為查詢字符串經過編碼的// 解碼klet k = decodeURIComponent(k_v[0]);// 解碼value 并保存到result對象中this.result[k] = decodeURIComponent(k_v[1]);}}// 解析后的結果// console.log(this.result);/*app_id: "2021000120612450"auth_app_id: "2021000120612450"charset: "utf-8"*/// 把地址欄上面的支付結果,再get請求轉發給后端this.$axios({method: 'get',// 攜帶同步回調的參數訪問后端url: this.$settings.base_url + '/order/success/' + location.search,}).then(response => {console.log(response.data)if (response.data) {this.$message({message: '支付結果同步成功'})} else {this.$message({message: '支付結果同步失敗'})}}).catch(() => {this.$message({message: '查詢訂單支付狀態失敗'})})},components: {Header,} } </script><style scoped> .main {padding: 60px 0;margin: 0 auto;width: 1200px;background: #fff; }.main .title {display: flex;-ms-flex-align: center;align-items: center;padding: 25px 40px;border-bottom: 1px solid #f2f2f2; }.main .title .success-tips {box-sizing: border-box; }.title img {vertical-align: middle;width: 60px;height: 60px;margin-right: 40px; }.title .success-tips {box-sizing: border-box; }.title .tips {font-size: 26px;color: #000; }.info span {color: #ec6730; }.order-info {padding: 25px 48px;padding-bottom: 15px;border-bottom: 1px solid #f2f2f2; }.order-info p {display: -ms-flexbox;display: flex;margin-bottom: 10px;font-size: 16px; }.order-info p b {font-weight: 400;color: #9d9d9d;white-space: nowrap; }.study {padding: 25px 40px; }.study span {display: block;width: 140px;height: 42px;text-align: center;line-height: 42px;cursor: pointer;background: #ffc210;border-radius: 6px;font-size: 16px;color: #fff; } </style> * 2. 設置PayCourse的路由 // 導入支付成功提示頁面 import PayCourse from "@/views/PayCourse"// 支付成功提示頁面 (與后臺中配置的一致) {path: '/pay/success',name: 'PayCourse',component: PayCourse }, # 前臺同步回調地址(沒有/結尾) RETURN_URL = LUFFY_URL + '/pay/success' * 3. vue中url地址中多余的/#/不刪除的話回調地址路由匹配不成功http://127.0.0.1:8080/pay/success 路由匹配不成功 // 路由中設置 mode: "history", 刪除url中多余的/#/ const router = new VueRouter({mode: "history",routes }) * 4. 購買課程測試, 購買成功之后回調地址攜帶參數 回調地址+參數 http://127.0.0.1:8080/pay/success? // 編碼格式 charset=utf8 // 訂單號 &out_trade_no=ffb82dd39bf043808c311b4ae983d9ec&method=alipay.trade.page.pay.return // 價格 &total_amount=39.00 // 簽名 &sign=KFPDpjwAGuUa73qsBctb0m9Nj0514PoQnxcMyH%2Fzw1rPmzY%2F1%2BTwH2v4BB1hSyc5%2BRV6TIc%2Bvz1SrchxtND20HU%2BOjLuT%2BxSlGK034OFUDpxtiy4ofkYWWqcvxz84sQ8BUifdGKWDFoH1cgagbJVGl3DO1Lce5HbsvNZY2GeYQTC%2BTIWCUMKCcWYP375%2FQFCFoMUWj6LNcIQk3rrKMNJm9B%2F%2BwryrcRMJT1cNZnuLhKiYTzcIFRWnDyVu4aVfKa3xJtIEbTKAqIVHK9LuV%2BkrkBqD1GAwOy1gwIlrkB4VJ%2BvwPuyRpmVzGK7zVq6JIVzZNVawjFBhduwuoG0eUH7oA%3D%3D // 支付寶流水號 &trade_no=2022060922001441860503213387 // 授權商戶的App id &auth_app_id=2021000120612450 // 版本 &version=1.0 // 應用id &app_id=2021000120612450 // 簽名類型 &sign_type=RSA2 // 賣方id &seller_id=2088621959339670 // 時間戳 ×tamp=2022-06-09+11%3A50%3A44#/ location.search 獲取 url后拼接的參數 先去除? 在按&切分得到元組, 元組的元素'k=v', 在遍歷元素, 按=切分得到一個元組 ['k', 'v'] 對k, v的字符進行解碼保存到一個對象中4.7 訂單狀態接口
1. 查詢訂單狀態接口 get請求 2. 修改訂飯狀態接口 post請求 * 1. 路由order.py的urls.py # 訂單狀態接口 re_path('success', views.SuccessView.as_view()) # 支付狀態 class SuccessView(APIView):@staticmethoddef get(request):# 從參數中獲取訂單號去數據庫中查詢訂單的狀態out_trade_no = request.query_params.get('out_trade_no')order_obj = models.Order.objects.filter(out_trade_no=out_trade_no).first()# 訂單狀態order_status = order_obj.order_status# 判斷訂單狀態if order_status != 1: # 1 為支付成功狀態return Response(False)return Response(True)# 支付寶異步回調(只能上線之后訪問, 支付寶無法訪問到我電腦本地的地址)@staticmethoddef post(request):# 導入支付類from lib.iPay import alipayfrom utils.logger import log# 獲取訂單號out_trade_no = request.data.get('out_trade_no')# 獲取支付時間pay_time = request.data.get('gmt_payment')# 驗證簽名 支付寶返回的urlencode格式被轉為queryset_dictdata = request.data.dict() # 裝為普通字段sign = data.pop('sign')success = alipay.verify(data, sign)# 驗證成功, 并且 data中trade_status的值必須是TRADE_SUCCESS或TRADE_FINISHEDif success and data['trade_status'] in ('TRADE_SUCCESS', 'TRADE_FINISHED'):# 更新訂單狀態與支付時間models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1, pay_time=pay_time)# 記錄日志log.info(f'{out_trade_no}支付成功')return Response('success') # 驗證成功必須返回successlog.info(f'{out_trade_no}支付失敗')return Response('error') # 驗證不成功5. 上傳到遠程倉庫
在Terminai中輸入git命令5.1 前端
# 添加到暫存區 PS D:\vue\luffy_vue> git add . # 提交到版本庫 PS D:\vue\luffy_vue> git commit -m '完成支付頁面' # 下拉 PS F:\synchro\Project\luffy> git pull origin dev # 上傳到遠端倉庫 PS F:\synchro\Project\luffy> git push origin dev5.2 后端
# 添加到暫存區 PS F:\synchro\Project\luffy> git add . # 提交到版本庫 PS F:\synchro\Project\luffy> git commit -m '完成支付功能' # 下拉 PS F:\synchro\Project\luffy> git pull origin dev # 上傳到遠端倉庫 PS F:\synchro\Project\luffy> git push origin dev總結
以上是生活随笔為你收集整理的5.学城项目 支付宝支付的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google Sketchup论坛
- 下一篇: Adobe Illustrator CS