Tips:一些记录,一些笔记

2024/06/05
WEDNESDAY
Eat to life to live, but not for food.
人生应为生而食,不应为食而生。

01
支付宝沙箱环境
支付宝沙箱环境「官方文档」:
https://opendocs.alipay.com/common/02kkv7?pathHash=8fd32ef6
开发文档:
https://opendocs.alipay.com/open/01bxlm
服务端开发文档:
https://opendocs.alipay.com/open/00a0ut?pathHash=b19b288a
服务端开发文档「电脑网站支付」:
https://opendocs.alipay.com/open/270/105898?pathHash=b3b2b667
开发工具:
https://opendocs.alipay.com/open/009ys9
沙箱应用:
https://open.alipay.com/develop/sandbox/app
沙箱工具:
https://open.alipay.com/develop/sandbox/tool/alipayclint
沙箱账号:
https://open.alipay.com/develop/sandbox/account
——————————————————
沙箱账号是支付宝自动分配的,其中的余额都是假的,你可以随意充值或提现,都只是数字的变化,用于测试你开发的网站或应用的「支付功能及其相关逻辑」是否运行正常:


而在开发中用到最多的诸如API的接口等等信息,都是通过「沙箱应用」提供出来的:


02
Django项目中使用「支付宝沙箱环境」
上面大概介绍了下支付宝的沙箱环境,下面在Django项目中使用它。
沙箱环境的配置项,我们集中到Django的全局配置文件「settings.py」中。
文件「settings.py」
# ======================================# 支付宝沙箱环境# ======================================# -----------------------# 支付宝网关地址# 支付宝沙箱网关地址,开发者在沙箱环境调用 OpenAPI 发送 http(s) 请求的目标地址,需配置在AlipayClient中。ALIPAY_SERVER_URL = "https://openapi-sandbox.dl.alipaydev.com/gateway.do"# -----------------------# APPIDALIPAY_APP_ID = "9021000137680887"# -----------------------# 接口加签方式:「公钥模式 - 私钥」ALIPAY_APP_PRIVATE_KEY = "MIIEpQIBAAKCAQEAu26zZ9ufwCpCxf88IVIEhz4jGsMTfnt9X1HNjJ/mog8KBTgE9PwxW7ehVYgAgkNP3jpMqSQzfouRNYeqhgbgNFCtE6CpFRSmapH8X0//dZ0i2AIX1xxGATwNB4KlzAohKQMLc4DHMMfBbptuVGdzIRl1o/m4PYDS9Fs6ko3diJNTKzA+L9SS/DZLQH6tsyp7RdstQ/LPo5/1Zol03+jShioRreHwmmI0PK3JjsYOb5arH+3tCVRf3lL3fWKavmT9Q2lFgZQmTg9C/466bRNoLYWeIV/Z9c0T9br3Mmj1/b7hbgz/pLWvvGrhVYvrsyrc2nLRrRZJ6SLt1Z7bcd6DmwIDAQABAoIBAASNk94Z/69zV1vn71X/4AxI/iFQS9DuvwTpv8yCtd+J7fxid/xbg/duZoTlYN3JNM95KxOQxY91ZHmgagwHbrrWmt23MWRJtCEymqbWEqaQwp/srReUoD63brmX+t8R/+w3HrgXkL8mmxRMwklamflb3Pe2wTr9PiKhdLLKr2752ozjwi/DBV1Pei8wKVjzYwYkRfAKFytmmlgFnl1n2BQStXDKAcuA9rJnuMVF4D5ZRVVuuR04vNMLJWyzbN/az8+Tvd/JIWD0svCO5IiXZKYkQ0pD1VCZNOe1hG7RFq79+15wADjrq7ztH6+BYmNed+gevq/htfseBtPdiuWHtIECgYEA4613083+8JmSrgeESheg4tZwG7ouhO/VCINfSAy2QUERE24yanqvKKb2ZJpUUTC3CqjYfjpJJnGWfqKVSpg72ObCWjMH96BBzX+xCVC2n8nb6cs9vwC8c3t3RZ55U86sABzYd3s6RSog3xPefN+bqGmfLNxRWDat61+PaiwNWHMCgYEA0r+ZsJHhrJ+b+g9vXkhxzO7jVk/fbyA2D9eXx/85Z3lkpjV7pq9WEXmrTkhHr7OyAnFJH7O7IvKHpzDbNeSaZt9L/gk9hdIhvLG9/6qGZHGqwrlUV/5OxuxeCu5ihyfkMF2HFDwnDJDdNPgtxYoYU4rFr1Pr4XvIUKEYpYW4ZjkCgYEAiBIki3GR7DnccCYN6jo07V07o1aidtDfgTcCuwzd4CIDEpnJh9nK1YDzwSx7OArlWnmO6eHr1g4M46JHl01kdz1y6nWVTWl56ldmdyrF+rmptRDC1mSaPGqN3g3YdT4PjgGvXblIGg4Tlz9IuVjqrrxxZV5HP3FugD2ceNfbIgECgYEAsJ0VJV376/iOdI8+25qGlCGWrxycuLob1Ebu0hJXfxnCeW/UwimqTVbTcvUr7lj/UwCxMTokF/N8L2bILm/33UJAoxIHVwB0eXfTLFEx8zc2zZ0Wy+85AYfQgASq2W1a1BVtQE3ww50bZGqfoS5LmKdxZ69CvGJJyxhWWVCo6jECgYEAqk8+cx2pnvF89zTkGXvvuC7898/ZyNBHnSNGO6dLRZE9heiEfvmjqDgVNoxDhQpObMxfv4HdBtHqd9gCF7Oxj4TbicCwaqytDtlLyQs3Ia7SGZ+U2PRf9kqfbm9OakI8UONDQ9JhMjuY7jspaoROjv+X5geHJDsnw1/5BGeu18Q="# -----------------------# 接口加签方式:「公钥模式 - 支付宝公钥」ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Zt8CyUbB+W+zk6a6zCFMFpf2QX4RGwDke9fg5WX9A4SiDffw9Q+M+wcclJglHO5/5v0DR0LcUz8MF0wVK4za/iP0YWFCRWy4vVRPfDVDkqOXaIr4cDr7e1q7ayX1y2LA0TQAYMWXdOEbCyehaJU1+C8Mgs/G0JnwSXuZ4h/BBprnfzG3/Lc6pAIwhH3KbiMaEZIRi8j0AlUIsoDZf834SDm7LusluhY3chG0vQI5BjbgWzVmoNB3XSenoJNhqjcoaC9abz9DWJkftr6Z92DMPFqF//AjEsDpQGWF2froRbXZOqHAumtDi5zm+xrP0AGsMUF5kwrEoGw2DNEyxGfCQIDAQAB"
03
支付宝的支付逻辑
官方文档地址:
https://opendocs.alipay.com/open/270/105899?pathHash=d57664bf
时序图:

调用流程如下:
1. 商家系统调用 alipay.trade.page.pay(统一收单下单并支付页面接口)向支付宝发起支付请求,支付宝对商家请求参数进行校验,而后重新定向至用户登录页面。
2. 用户确认支付后,支付宝通过 get 请求 returnUrl(商户入参传入),返回同步返回参数。
3. 交易成功后,支付宝通过 post 请求 notifyUrl(商户入参传入),返回异步通知参数。
4. 若由于网络等原因,导致商家系统没有收到异步通知,商家可自行调用 alipay.trade.query(统一收单交易查询接口)查询交易以及支付信息(商家也可以直接调用该查询接口,不需要依赖异步通知)。
注意:
• 由于同步返回的不可靠性,支付结果必须以异步通知或查询接口返回为准,不能依赖同步跳转。
• 商家系统接收到异步通知以后,必须通过验签(验证通知中的 sign 参数)来确保支付通知是由支付宝发送的。详细验签规则可查看 异步通知验签。
• 接收到异步通知并验签通过后,请务必核对通知中的 app_id、out_trade_no、total_amount 等参数值是否与请求中的一致,并根据 trade_status 进行后续业务处理。
• 在支付宝端,partnerId 与 out_trade_no 唯一对应一笔单据,商家端保证不同次支付 out_trade_no 不可重复;若重复,支付宝会关联到原单据,基本信息一致的情况下会以原单据为准进行支付。
04
实现一个支付功能的「样例」
官方示例文档:
https://opendocs.alipay.com/open/02no41?pathHash=2faf7eac
首先,通过PIP安装支付沙箱的PyPI依赖包「pip install alipay-sdk-python」:
https://pypi.org/project/alipay-sdk-python/

代码如下:
(venv) (base) adamhuan@Leviathan django_daily_media % pip install alipay-sdk-pythonLooking in indexes: https://mirrors.aliyun.com/pypi/simple/Collecting alipay-sdk-pythonDownloading https://mirrors.aliyun.com/pypi/packages/78/c2/517f8f9a9714b742f8cb1040e45ac7961ebd0d9f02ca3709ac38d48616a5/alipay_sdk_python-3.7.137-py3-none-any.whl (22.0 MB)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 22.0/22.0 MB 344.4 kB/s eta 0:00:00Collecting pycryptodome (from alipay-sdk-python)Downloading https://mirrors.aliyun.com/pypi/packages/ff/96/b0d494defb3346378086848a8ece5ddfd138a66c4a05e038fca873b2518c/pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl (2.4 MB)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.4/2.4 MB 342.1 kB/s eta 0:00:00Collecting rsa (from alipay-sdk-python)Downloading https://mirrors.aliyun.com/pypi/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl (34 kB)Collecting pyasn1>=0.1.3 (from rsa->alipay-sdk-python)Downloading https://mirrors.aliyun.com/pypi/packages/23/7e/5f50d07d5e70a2addbccd90ac2950f81d1edd0783630651d9268d7f1db49/pyasn1-0.6.0-py2.py3-none-any.whl (85 kB)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 85.3/85.3 kB 352.0 kB/s eta 0:00:00Installing collected packages: pycryptodome, pyasn1, rsa, alipay-sdk-pythonSuccessfully installed alipay-sdk-python-3.7.137 pyasn1-0.6.0 pycryptodome-3.20.0 rsa-4.9(venv) (base) adamhuan@Leviathan django_daily_media %
该依赖包的PyPI页面下有给出一个官方样例:

这段代码需要注意的是,前面我们在时序图中可以看到,使用的接口是「alipay.trade.page.pay」
因此,在这段代码中,接口样例中,只需要选择对应的接口代码即可,而不需要全部选择。
关于接口「alipay.trade.page.pay」官方的接口文档是:
https://opendocs.alipay.com/open/028r8t?scene=22
最终,我们本地的样例代码如下所示:
#!/usr/bin/env python# -*- coding: UTF-8 -*-# ——————————————————# 脚本说明:# xxxxxxxxx# ——————————————————# ========================================# 开始# ))))))))) 模块包导入import loggingimport tracebackimport osimport syssys.path.append('/Users/adamhuan/PycharmProjects/django_daily_media')os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_daily_media.settings')from django.conf import settingsfrom alipay.aop.api.AlipayClientConfig import AlipayClientConfigfrom alipay.aop.api.DefaultAlipayClient import DefaultAlipayClientfrom alipay.aop.api.FileItem import FileItemfrom alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModelfrom alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModelfrom alipay.aop.api.domain.AlipayTradePayModel import AlipayTradePayModelfrom alipay.aop.api.domain.GoodsDetail import GoodsDetailfrom alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfofrom alipay.aop.api.domain.SettleInfo import SettleInfofrom alipay.aop.api.domain.SubMerchant import SubMerchantfrom alipay.aop.api.request.AlipayOfflineMaterialImageUploadRequest import AlipayOfflineMaterialImageUploadRequestfrom alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequestfrom alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequestfrom alipay.aop.api.request.AlipayTradePayRequest import AlipayTradePayRequestfrom alipay.aop.api.response.AlipayOfflineMaterialImageUploadResponse import AlipayOfflineMaterialImageUploadResponsefrom alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse# ))))))))) 日志logging.basicConfig(level=logging.INFO,format='%(asctime)s %(levelname)s %(message)s',filemode='a',)logger = logging.getLogger('')# ))))))))) 类名# ))))))))) 执行阶段if __name__ == '__main__':"""设置配置,包括支付宝网关地址、app_id、应用私钥、支付宝公钥等,其他配置值可以查看AlipayClientConfig的定义。"""alipay_client_config = AlipayClientConfig()# =================# Default# =================# 旧版本的API:openapi.alipay.com# 新版本的API:openapi-sandbox.dl.alipaydev.com# alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'# alipay_client_config.app_id = '[your app_id]'# alipay_client_config.app_private_key = '[your app private key]'# alipay_client_config.alipay_public_key = '[alipay public key]'# =================# For Django Custom# =================alipay_client_config.server_url = settings.ALIPAY_SERVER_URLalipay_client_config.app_id = settings.ALIPAY_APP_IDalipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEYalipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY# 输出print("")print("==========================================")print(settings.ALIPAY_SERVER_URL)"""得到客户端对象。注意,一个alipay_client_config对象对应一个DefaultAlipayClient,定义DefaultAlipayClient对象后,alipay_client_config不得修改,如果想使用不同的配置,请定义不同的DefaultAlipayClient。logger参数用于打印日志,不传则不打印,建议传递。"""client = DefaultAlipayClient(alipay_client_config=alipay_client_config, logger=logger)"""页面接口示例:alipay.trade.page.pay「选择需要的接口」"""# 对照接口文档,构造请求对象model = AlipayTradePagePayModel()# 请求参数:必选model.out_trade_no = "pay201805020000226"model.total_amount = 50model.subject = "测试"model.body = "支付宝测试"model.product_code = "FAST_INSTANT_TRADE_PAY"# 请求参数:可选# settle_detail_info = SettleDetailInfo()# settle_detail_info.amount = 50# settle_detail_info.trans_in_type = "userId"# settle_detail_info.trans_in = "2088302300165604"# settle_detail_infos = list()# settle_detail_infos.append(settle_detail_info)# settle_info = SettleInfo()# settle_info.settle_detail_infos = settle_detail_infos# model.settle_info = settle_info# sub_merchant = SubMerchant()# sub_merchant.merchant_id = "2088301300153242"# model.sub_merchant = sub_merchant# 构造的请求request = AlipayTradePagePayRequest(biz_model=model)# 得到构造的请求,如果http_method是GET,则是一个带完成请求参数的url,如果http_method是POST,则是一段HTML表单片段response = client.page_execute(request, http_method="GET")# 输出print("")print("==========================================")print("alipay.trade.page.pay response:" + response)# ))))))))) 结束# ========================================# 结束
以命令行的方式运行:

运行结果:
/Users/adamhuan/PycharmProjects/django_daily_media/venv/bin/python Users/adamhuan/PycharmProjects/django_daily_media/utils/alipay_example.py==========================================https://openapi-sandbox.dl.alipaydev.com/gateway.do2024-06-05 12:18:39,853 INFO [afc3b990-22f2-11ef-8ece-f2557eb4cbe8]request:https://openapi-sandbox.dl.alipaydev.com/gateway.do?app_id=9021000137680887&biz_content={"body":"支付宝测试","out_trade_no":"pay201805020000226","product_code":"FAST_INSTANT_TRADE_PAY","subject":"测试","total_amount":50}&charset=utf-8&format=json&method=alipay.trade.page.pay&sign_type=RSA2×tamp=2024-06-05 12:18:39&version=1.0&sign=HCM1xQTmZOyPx31tH9MIYM+Q5eTDtopBRWmTIeHbc2vcjFrEDPe8rW3010N1Z/EpsMvzCDwMLkJqErTOMtefB+NKnL0EGRQUFlM1gut575nTX9qXHLCC+ySRSL8lgftLAgNaOR1JtGcewBgQnMRTUJk5TGDOFYNMA7ANTKgthbXFmTOt3Zieki+awT0B+NQGmckHx1OqCrjY8lsge1yPkhrHUa82djw/AtdoEKNee2CnCYZ1+wSkQb2aM0zS6Idf60wSWfj9KnSEO5fjLeSdgogLiEHt7rSXpOWVfBK6iYT8H+VACek56fGHEZrVt0aEV2d2g0OOpLlk1dmgyfFoHA============================================alipay.trade.page.pay response:https://openapi-sandbox.dl.alipaydev.com/gateway.do?timestamp=2024-06-05+12%3A18%3A39&app_id=9021000137680887&method=alipay.trade.page.pay&charset=utf-8&format=json&version=1.0&sign_type=RSA2&sign=HCM1xQTmZOyPx31tH9MIYM%2BQ5eTDtopBRWmTIeHbc2vcjFrEDPe8rW3010N1Z%2FEpsMvzCDwMLkJqErTOMtefB%2BNKnL0EGRQUFlM1gut575nTX9qXHLCC%2BySRSL8lgftLAgNaOR1JtGcewBgQnMRTUJk5TGDOFYNMA7ANTKgthbXFmTOt3Zieki%2BawT0B%2BNQGmckHx1OqCrjY8lsge1yPkhrHUa82djw%2FAtdoEKNee2CnCYZ1%2BwSkQb2aM0zS6Idf60wSWfj9KnSEO5fjLeSdgogLiEHt7rSXpOWVfBK6iYT8H%2BVACek56fGHEZrVt0aEV2d2g0OOpLlk1dmgyfFoHA%3D%3D&biz_content=%7B%22body%22%3A%22%E6%94%AF%E4%BB%98%E5%AE%9D%E6%B5%8B%E8%AF%95%22%2C%22out_trade_no%22%3A%22pay201805020000226%22%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%2C%22subject%22%3A%22%E6%B5%8B%E8%AF%95%22%2C%22total_amount%22%3A50%7DProcess finished with exit code 0
然后,在浏览器中打开最后输出的链接「alipay.trade.page.pay response」




注意,上面的商家信息与买家信息都是之前在「沙箱环境」的「沙箱账户」中提供的。
它们之前的状态:

在上述虚拟交易完成后的状态:

可以非常明确得看到:
商家多了50
买家少了50
如果接口返回的链接你已经执行了「交易动作」,再次访问的时候会遇到错误:

至此,一个简单的支付宝「沙箱环境」的「交易功能」的演示,就完成了。
——————————————————
为了让上述代码可以更方便的在Django中运行,我们对其进行修改,修改成「类」的方式:
#!/usr/bin/env python# -*- coding: UTF-8 -*-# ——————————————————# 脚本说明:# 支付方法:# 使用「支付宝」的「沙箱环境」# ——————————————————# ========================================# 开始# ))))))))) 模块包导入import loggingimport traceback# 『单独运行』使用# 如果「settings」来自Django,需要社会环境变量# import os# import sys# sys.path.append('/Users/adamhuan/PycharmProjects/django_daily_media')# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_daily_media.settings')# 「结合Django」使用from django.conf import settingsfrom alipay.aop.api.AlipayClientConfig import AlipayClientConfigfrom alipay.aop.api.DefaultAlipayClient import DefaultAlipayClientfrom alipay.aop.api.FileItem import FileItemfrom alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModelfrom alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModelfrom alipay.aop.api.domain.AlipayTradePayModel import AlipayTradePayModelfrom alipay.aop.api.domain.GoodsDetail import GoodsDetailfrom alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfofrom alipay.aop.api.domain.SettleInfo import SettleInfofrom alipay.aop.api.domain.SubMerchant import SubMerchantfrom alipay.aop.api.request.AlipayOfflineMaterialImageUploadRequest import AlipayOfflineMaterialImageUploadRequestfrom alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequestfrom alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequestfrom alipay.aop.api.request.AlipayTradePayRequest import AlipayTradePayRequestfrom alipay.aop.api.response.AlipayOfflineMaterialImageUploadResponse import AlipayOfflineMaterialImageUploadResponsefrom alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse# ))))))))) 日志logging.basicConfig(level=logging.INFO,format='%(asctime)s %(levelname)s %(message)s',filemode='a',)logger = logging.getLogger('')# ))))))))) 类名# ))))))))) 执行阶段class Alipay():"""设置配置,包括支付宝网关地址、app_id、应用私钥、支付宝公钥等,其他配置值可以查看AlipayClientConfig的定义。"""alipay_client_config = AlipayClientConfig()# =================# Default# =================# 旧版本的API:openapi.alipay.com# 新版本的API:openapi-sandbox.dl.alipaydev.com# alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'# alipay_client_config.app_id = '[your app_id]'# alipay_client_config.app_private_key = '[your app private key]'# alipay_client_config.alipay_public_key = '[alipay public key]'# =================# For Django Custom# =================alipay_client_config.server_url = settings.ALIPAY_SERVER_URLalipay_client_config.app_id = settings.ALIPAY_APP_IDalipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEYalipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY# 输出print("")print("==========================================")print(settings.ALIPAY_SERVER_URL)def __init__(self):"""得到客户端对象。注意,一个alipay_client_config对象对应一个DefaultAlipayClient,定义DefaultAlipayClient对象后,alipay_client_config不得修改,如果想使用不同的配置,请定义不同的DefaultAlipayClient。logger参数用于打印日志,不传则不打印,建议传递。"""self.client = DefaultAlipayClient(alipay_client_config=self.alipay_client_config,logger=logger)def trade_page(self,out_trade_no,total_amount,subject,body,product_code):"""页面接口示例:alipay.trade.page.pay「选择需要的接口」"""# 对照接口文档,构造请求对象model = AlipayTradePagePayModel()# 请求参数:必选# model.out_trade_no = "pay202406051355202"# model.total_amount = 50# model.subject = "测试"# model.body = "支付宝测试"# model.product_code = "FAST_INSTANT_TRADE_PAY"model.out_trade_no = out_trade_nomodel.total_amount = total_amountmodel.subject = subjectmodel.body = bodymodel.product_code = product_code# 请求参数:可选# settle_detail_info = SettleDetailInfo()# settle_detail_info.amount = 50# settle_detail_info.trans_in_type = "userId"# settle_detail_info.trans_in = "2088302300165604"# settle_detail_infos = list()# settle_detail_infos.append(settle_detail_info)# settle_info = SettleInfo()# settle_info.settle_detail_infos = settle_detail_infos# model.settle_info = settle_info# sub_merchant = SubMerchant()# sub_merchant.merchant_id = "2088301300153242"# model.sub_merchant = sub_merchant# 构造的请求request = AlipayTradePagePayRequest(biz_model=model)# 得到构造的请求,如果http_method是GET,则是一个带完成请求参数的url,如果http_method是POST,则是一段HTML表单片段response = self.client.page_execute(request, http_method="GET")# 输出# print("")# print("==========================================")# print("alipay.trade.page.pay response:" + response)# 返回阶段return response# ))))))))) 结束# ========================================# 结束
在我的项目中,它位于:

05
Django中定义「订单」相关的数据模型
上面只是一个官方例子的演示,为了在Django中更好的使用到支付宝的沙箱环境,我们需要定义一个「订单表」以及与其相关的数据逻辑,具体如下。
数据模型类「models.py」
from django.db import modelsfrom account.models import Profile# Create your models here.class Card(models.Model):card_name = models.CharField(verbose_name="会员卡名称",max_length=400,unique=True,)card_price = models.DecimalField(verbose_name="价格",max_digits=8,decimal_places=2,)duration = models.IntegerField(verbose_name="有效天数",)info = models.CharField(verbose_name="简介",max_length=200,null=True,blank=True,)color = models.CharField(verbose_name="颜色",max_length=200,null=True,blank=True,)created_at = models.DateTimeField(verbose_name="创建时间",auto_now_add=True,editable=True,)updated_at = models.DateTimeField(verbose_name="更新时间",auto_now=True,editable=True,)class Meta:verbose_name = "会员卡信息"verbose_name_plural = verbose_namedef __str__(self):return self.card_nameclass Order(models.Model):ORDER_STATUS = (("TRADE_SUCCESS", "成功"),("TRADE_CLOSED", "超时关闭"),("WAIT_BUYER_PAY", "交易创建"),("TRADE_FINISHED", "交易结束"),("PAYING", "待支付"),)PAY_TYPE = (("alipay", "支付宝"),("wechat", "微信"),)# 用户user = models.ForeignKey(verbose_name="用户",to=Profile,related_name='orders',to_field='uid',on_delete=models.CASCADE,)# 禁止反向card = models.ForeignKey(verbose_name="会员卡",to=Card,related_name='+',on_delete=models.DO_NOTHING,)# 订单号(唯一)order_sn = models.CharField(verbose_name="订单编号",max_length=30,null=True,blank=True,unique=True,)# 支付宝交易号trade_no = models.CharField(verbose_name="支付宝交易号",max_length=100,null=True,blank=True,unique=True,)# 支付状态pay_status = models.CharField(verbose_name="订单状态",choices=ORDER_STATUS,default="PAYING",max_length=30,)# 支付类型pay_type = models.CharField(verbose_name="支付类型",choices=PAY_TYPE,default='alipay',max_length=10,)order_mount = models.DecimalField(verbose_name="订单金额",max_digits=10,decimal_places=2,)pay_time = models.DateTimeField(verbose_name="支付时间",null=True,blank=True,)# 创建时间created_at = models.DateTimeField(verbose_name="创建时间",auto_now_add=True,editable=True,)# 更新时间updated_at = models.DateTimeField(verbose_name="更新时间",auto_now=True,editable=True,)class Meta:verbose_name="订单信息"verbose_name_plural=verbose_namedef __str__(self):return self.order_sn
序列化「serializers.py」
#!/usr/bin/env python# -*- coding: UTF-8 -*-# ——————————————————# 脚本说明:# xxxxxxxxx# ——————————————————# ========================================# 开始# ))))))))) 模块包导入from rest_framework import serializersfrom trade.models import *# ))))))))) 类名class CardSerializer(serializers.ModelSerializer):class Meta:model = Cardfields = '__all__'# fields = [# 'card_name',# 'card_price',# 'info',# 'color',# 'duration',# ]class OrderSerializer(serializers.ModelSerializer):class Meta:model = Orderfields = '__all__'# ))))))))) 执行阶段# ))))))))) 结束# ========================================# 结束
权限「permissions.py」
#!/usr/bin/env python# -*- coding: UTF-8 -*-# ——————————————————# 脚本说明:# xxxxxxxxx# ——————————————————# ========================================# 开始# ))))))))) 模块包导入from rest_framework import permissions# ))))))))) 类名class IsAdminUserOrReadonly(permissions.BasePermission):def has_permission(self, request, view):if request.method in permissions.SAFE_METHODS:return Truereturn request.user.is_supperuser# ))))))))) 执行阶段# ))))))))) 结束# ========================================# 结束
Django管理类「admin.py」
from django.contrib import adminfrom trade.models import *# Register your models here.@admin.register(Card)class CardAdmin(admin.ModelAdmin):list_display = ['card_name','card_price','duration','info','color','created_at','updated_at',]@admin.register(Order)class OrderAdmin(admin.ModelAdmin):list_display = ['user','card','order_sn','trade_no','pay_status','pay_type','order_mount','pay_time','created_at','updated_at',]
06
Django中的支付流程设计
在我当前的Django项目中,支付流程的起点是「会员卡」管理页面:

当点击了具体的会员卡的卡片后,会触发对应的点击事件,进而调用触发的方法,跳转到支付宝沙箱环境的支付页面。
而跳转「支付页面」之前,我们应该构造好支付接口所需要的相关参数:

因此,在Django中需要配置新的路由,以关联具体的处理函数。
Django全局路由文件「urls.py」
"""URL configuration for django_daily_media project.The `urlpatterns` list routes URLs to views. For more information please see:https://docs.djangoproject.com/en/5.0/topics/http/urls/Examples:Function views1. Add an import: from my_app import views2. Add a URL to urlpatterns: path('', views.home, name='home')Class-based views1. Add an import: from other_app.views import Home2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')Including another URLconf1. Import the include() function: from django.urls import include, path2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))"""from django.contrib import adminfrom django.urls import path, includefrom rest_framework.routers import DefaultRouter# from movie.views import *# from account.views import *from movie import views as movie_viewfrom account import views as account_viewfrom trade import views as trade_view# 这里注册的地址,在后期访问的时候,都需要加上前缀「/api/xxxx」router = DefaultRouter()router.register('movie', movie_view.MovieViewSet, 'movie')router.register('category', movie_view.CategoryViewSet, 'category')router.register('collects', account_view.CollectViewSet, 'collects')router.register('card', trade_view.CardViewSet, 'card')router.register('order', trade_view.OrderViewset, 'order')urlpatterns = [# Django Adminpath("admin/", admin.site.urls),# Django Rest Frameworkpath("api/", include(router.urls)),# Django Djoserpath('auth/', include('djoser.urls')),# Django Token Based Authentication# path('auth/', include('djoser.urls.authtoken')),# Django Djoser SWTpath("jwt/", include("djoser.urls.jwt")),# Account: Activationpath("activate/<str:uid>/<str:token>/", account_view.account_activate),# Account: Password Reset# path("password_reset/<str:uid>/<str:token>/", account_password_reset),# 支付博快:Alipaypath("api/alipay/", trade_view.AlipayAPIView.as_view()),]
从上面代码可以看到,因为与支付相关,所以新增的路由「/api/alipay」的对应视图设置在了「trade」应用中:

接下来是「trade」中对应视图的设计,它的完整代码如下:
from django.shortcuts import renderfrom django.utils import timezonefrom django_filters import rest_framework as filtersfrom datetime import datetimefrom rest_framework import viewsets,statusfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework import paginationfrom trade.models import *from trade.serializers import *from trade.permissions import *from account.models import *from utils.error import *from utils.common import *from utils.payment_alipay import *# Create your views here.# Cardclass CardFilter(filters.FilterSet):card_name = filters.CharFilter(lookup_expr='icontains')duration = filters.NumberFilter()class Meta:model = Cardfields = ['card_name', 'duration']class CardPagination(pagination.PageNumberPagination):page_size = 12max_page_size = 20page_size_query_param = 'page_size'class CardViewSet(viewsets.ModelViewSet):queryset = Card.objects.all()serializer_class = CardSerializerfilter_backends = (filters.DjangoFilterBackend,)filterset_class = CardFilterpagination_class = CardPagination# permission_classes = [# IsAdminUserOrReadonly,# ]# Orderclass OrderFilter(filters.FilterSet):pay_status = filters.CharFilter(lookup_expr='icontains')pay_type = filters.CharFilter(lookup_expr='icontains')class Meta:model = Orderfields = ['pay_status', 'pay_type']class OrderPagination(pagination.PageNumberPagination):page_size = 12max_page_size = 20page_size_query_param = 'page_size'class OrderViewset(viewsets.ModelViewSet):queryset = Order.objects.all()serializer_class = OrderSerializerfilter_backends = (filters.DjangoFilterBackend,)filterset_class = OrderFilterpagination_class = OrderPaginationpermission_classes = [IsAdminUserOrReadonly,]# Alipayclass AlipayAPIView(APIView):# 发起支付宝支付「场景」:# 1. 创建新的支付# 2. 支付未完成的订单def get(self, request):# 判断卡号「card_id」是否正确card_id = request.GET.get("card_id", None)print("================")print(card_id)try:card = Card.objects.get(id=card_id)except:return Response(response_data(*TradeError.CardParamError))# 订单号out_trade_no = "pay" + datetime.now().strftime("%Y%m%d%H%M%S") + get_random_code(4)# 创建订单try:Order.objects.create(user = Profile.objects.get(user=request.user),card = card,order_sn = out_trade_no,order_mount = card.card_price,pay_time = timezone.now(),)except:return Response(response_data(*TradeError.OrderCreateError))# 请求支付宝try:alipay = Alipay()pay_url = alipay.trade_page(out_trade_no=out_trade_no,total_amount=str(card.card_price),subject=card.card_name,body="「支付宝 - 沙箱环境」支付",product_code="FAST_INSTANT_TRADE_PAY")return Response(pay_url)except:return Response(response_data(*TradeError.AlipayRequestError))
至此,Django后端的接口就集成完成了。
下面,还有Vue3前端代码,实现支付的前端页面。
07
Vue.JS前端页面
在前端页面的会员卡页面中,我是通过自定义组件完成的三张会员卡的渲染的:

而页面点击是通过子组件暴露出来的事件,最终传递到子组件内部进行事件处理的「@child-event」
它在子组件中的定义是:


因此,前端调用后端的「沙箱环境」的代码将写在这一部分。
它的完整代码如下所示:
<template><a-badge-ribbon :text="card.card_price + ' RMB/年'" color="cyan" :style="{top: '15px', right: '2px'}"><div class="card-container"><div class="card" @click="emitChildEvent"><!--<div class="card-header"><h3>{{ title }}</h3></div>--><div class="card-body"><div class="level-badge" :class="levelClass"><!-- <span style="color: white;font-weight: bold">{{ level }}</span> -->{{ card.card_name }}</div><h2 style="color: black"><slot></slot></h2></div></div></div></a-badge-ribbon></template><script>import axios from 'axios'export default {name: 'AdvancedCard',props: {// title: String,// level: String,// color: String,// price: String,card: '',},computed: {levelClass() {const levelToClassMap = {'黄金VIP': 'low-level','白金VIP': 'mid-level','星钻VIP': 'high-level'};return levelToClassMap[this.card.card_name] || 'low-level';},},methods: {// 将子组件的事件传递到父组件emitChildEvent() {const message = "选择的等级「" + this.level + "」"console.log(this.card)console.log(this.card.id)axios// .get('/api/alipay', {card_id: this.card.id}).get('/api/alipay/?card_id=' + this.card.id).then(response => {console.log(response.data)// 跳转到支付页面window.location.href = response.data})this.$emit('child-event', message)}}};</script><style scoped>.card-container {display: flex;justify-content: center;}.card {width: 300px;margin: 10px;padding: 10px;background-color: #f0f0f0;border: 1px solid #ddd;border-radius: 4px;}.card:hover {box-shadow: 0 8px 16px 0 rgba(0,0,0,0.8);}.card-header {background-color: #42b983;color: white;padding: 5px;border-radius: 4px 4px 0 0;}.card-body {padding: 10px;}.level-badge {padding: 2px 5px;border-radius: 4px;margin-bottom: 5px;text-align: center;}.low-level {background-color: gold;color: white;font-weight: bold;}.mid-level {background-color: lightcoral;color: white;font-weight: bold;}.high-level {background-color: rebeccapurple;color: white;font-weight: bold;}</style>
——————————————————
以上,步骤如果都完成了,那么你就实现了:

在会员卡的界面点击具体的会员卡并跳转支付宝的沙箱支付的前后端流程。
但目前依旧存在的问题是,当你跳转支付界面,支付成功后,并不能自动的回到Django项目的页面,并且,当前的Django项目无法得到来自支付宝反馈的支付成功或失败的信息。

如上,支付成功后,无法自动跳转回来。
08
内网穿透
支付宝的支付消息反馈回来的时候,需要有公网地址。
但公网的IP地址与服务器资源通常不是免费的,并且操作起来肯定没有本地调试方便。
因此,为了让公网用户可以访问本地资源,你需要「内网穿透」的解决方案。
市面上有很多的内网穿透的产品与解决方案,百度、谷歌,可以找到一大堆。
这里,使用的是「cpolar」。
——————————————————
CPOLAR官方网站:
https://www.cpolar.com/
官方下载地址:
https://www.cpolar.com/download
虽然收费,但免费的权限已经够用:

当你注册成功后,就可以下载、解压、使用了:

看起来挺简单的,.. 下面来具体试试。
下载对应操作系统的软件包后,然后打开命令行窗口:
(base) adamhuan@Leviathan cpolar % pwd/Users/adamhuan/cpolar(base) adamhuan@Leviathan cpolar %(base) adamhuan@Leviathan cpolar % ls -ltrtotal 14144-rw-r--r--@ 1 adamhuan staff 7241308 6 5 15:07 cpolar-stable-darwin-arm64.zip(base) adamhuan@Leviathan cpolar %(base) adamhuan@Leviathan cpolar % unzip cpolar-stable-darwin-arm64.zipArchive: cpolar-stable-darwin-arm64.zipinflating: cpolar(base) adamhuan@Leviathan cpolar %(base) adamhuan@Leviathan cpolar % ls -ltrtotal 52752-rwxr-xr-x@ 1 adamhuan staff 19764592 11 5 2022 cpolar-rw-r--r--@ 1 adamhuan staff 7241308 6 5 15:07 cpolar-stable-darwin-arm64.zip(base) adamhuan@Leviathan cpolar %(base) adamhuan@Leviathan cpolar % ./cpolar helpUsage: ./cpolar <command> [OPTIONS] <local port or address>Options:Examples:cpolar http 80 # secure public URL for port 80 website servercpolar http -subdomain=baz 8080 # port 8080 available at baz.cpolar.iocpolar http foo.dev:80 # tunnel to host:port instead of localhostcpolar tcp 22 # tunnel arbitrary TCP traffic to port 22cpolar http -hostname="example.com" -httpauth="user:password" 10.0.0.1:8080cpolar ftp 21 # tunnel to port 21 for ftp servercpolar tls 443 # create tls tunnelcpolar service install # install cpolar daemoncpolar service start # start cpolar daemoncpolar service stop # stop cpolar daemoncpolar service uninstall # uninstall cpolar daemonAdvanced usage: cpolar <command> [OPTIONS] [command args] [...]Commands:cpolar authtoken Save authtoken to configuration filecpolar http Start an http tunnelcpolar tcp Start a tcp tunnelcpolar start [tunnel] [...] Start tunnels by name from config filecpolar start-all Start all tunnels defined in config filecpolar list List tunnel names from config filecpolar help Print helpcpolar version Print cpolar versioncpolar ftp Start a ftp tunnelcpolar tls Start a tls tunnelcpolar service <install|start|stop|uninstall> Daemon ManagerExamples:cpolar start www api blog pubsubcpolar start ssh -log=stdout -config=cpolar.ymlcpolar start-allcpolar versioncpolar service installcpolar service startcpolar service stopcpolar service uninstallcpolar http 8080cpolar is a command line application, try typing 'cpolar http 8080'at this terminal prompt to expose port 8080.(base) adamhuan@Leviathan cpolar %
然后通过官方提供的Token连接到自己的账户:
(base) adamhuan@Leviathan cpolar % ./cpolar authtoken ZGQxNmRmM2UtMDQzNS00MGJlLTkxxxxxxxxxxxOWJmNmZkAuthtoken saved to configuration file: /Users/adamhuan/.cpolar/cpolar.yml(base) adamhuan@Leviathan cpolar %
进行服务的安装:
(base) adamhuan@Leviathan cpolar % sudo ./cpolar service installPassword:(base) adamhuan@Leviathan cpolar %


启动:
(base) adamhuan@Leviathan cpolar % sudo ./cpolar service start(base) adamhuan@Leviathan cpolar %
浏览器:
http://localhost:9200

登录后:

创建一个针对当前Django项目的隧道:

高级中,设置「Host头域」:

最后,点击「创建」,创建成功后,就可以在「隧道列表」中看到了:

这时候去看「在线隧道列表」:

你就可以看到它们对应的公网地址了。
来试试用公网IP访问:
内网IP:
http://localhost:8000/api/card/

公网IP:
https://fc53c77.r9.cpolar.top/api/card/

可以看到,「内网穿透」配置成功了。
需要注意的是,由于我们使用的内网穿透是免费的版本,所以很可能会有有效期,有效期过了之后,公网地址将会失效;
在配置支付宝的回调与通知地址之前,请确认「穿透」地址的有效性。
09
支付宝沙箱「通知地址」
在「沙箱应用」中配置「授权回调地址」:
https://open.alipay.com/develop/sandbox/app

可以看到,在这里可以配置回调与通知的地址。
但是,更确定的方法是在代码的请求中进行配置。
10
Django中配置「通知」
配置路由:

然后,在Django的全局配置「settings.py」中设置相关的变量(完整的回调与通知地址):
# 回调地址 Return URLALIPAY_RETURN_URL = "http://6baab108.r10.cpolar.top/orders/"# 通知地址 Notify URLALIPAY_NOTIFY_URL = "http://6baab108.r10.cpolar.top/api/callback/"# 商家IDALIPAY_SELLER_PID = '2088721036954856'
由于新增了回调与通知地址的配置,因此在构造「支付宝请求」的时候,需要增加新的属性,以确保请求「一定拿到」交易成功或失败后的「通知地址」或「回调地址」
更新后的支付宝的自定义工具类完整代码如下所示:
「utils/payment_alipay.py」

#!/usr/bin/env python# -*- coding: UTF-8 -*-# ——————————————————# 脚本说明:# 支付方法:# 使用「支付宝」的「沙箱环境」# ——————————————————# ========================================# 开始# ))))))))) 模块包导入import loggingimport traceback# 『单独运行』使用# 如果「settings」来自Django,需要社会环境变量# import os# import sys# sys.path.append('/Users/adamhuan/PycharmProjects/django_daily_media')# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_daily_media.settings')# 「结合Django」使用from django.conf import settingsfrom alipay.aop.api.AlipayClientConfig import AlipayClientConfigfrom alipay.aop.api.DefaultAlipayClient import DefaultAlipayClientfrom alipay.aop.api.FileItem import FileItemfrom alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModelfrom alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModelfrom alipay.aop.api.domain.AlipayTradePayModel import AlipayTradePayModelfrom alipay.aop.api.domain.GoodsDetail import GoodsDetailfrom alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfofrom alipay.aop.api.domain.SettleInfo import SettleInfofrom alipay.aop.api.domain.SubMerchant import SubMerchantfrom alipay.aop.api.request.AlipayOfflineMaterialImageUploadRequest import AlipayOfflineMaterialImageUploadRequestfrom alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequestfrom alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequestfrom alipay.aop.api.request.AlipayTradePayRequest import AlipayTradePayRequestfrom alipay.aop.api.response.AlipayOfflineMaterialImageUploadResponse import AlipayOfflineMaterialImageUploadResponsefrom alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse# ))))))))) 日志logging.basicConfig(level=logging.INFO,format='%(asctime)s %(levelname)s %(message)s',filemode='a',)logger = logging.getLogger('')# ))))))))) 类名# ))))))))) 执行阶段class Alipay():"""设置配置,包括支付宝网关地址、app_id、应用私钥、支付宝公钥等,其他配置值可以查看AlipayClientConfig的定义。"""alipay_client_config = AlipayClientConfig()# =================# Default# =================# 旧版本的API:openapi.alipay.com# 新版本的API:openapi-sandbox.dl.alipaydev.com# alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'# alipay_client_config.app_id = '[your app_id]'# alipay_client_config.app_private_key = '[your app private key]'# alipay_client_config.alipay_public_key = '[alipay public key]'# =================# For Django Custom# =================alipay_client_config.server_url = settings.ALIPAY_SERVER_URLalipay_client_config.app_id = settings.ALIPAY_APP_IDalipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEYalipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY# alipay_client_config.notify_url = settings.ALIPAY_NOTIFY_URL# alipay_client_config.return_url = settings.ALIPAY_RETURN_URL# 输出# print("")# print("==========================================")# print(settings.ALIPAY_SERVER_URL)def __init__(self):"""得到客户端对象。注意,一个alipay_client_config对象对应一个DefaultAlipayClient,定义DefaultAlipayClient对象后,alipay_client_config不得修改,如果想使用不同的配置,请定义不同的DefaultAlipayClient。logger参数用于打印日志,不传则不打印,建议传递。"""self.client = DefaultAlipayClient(alipay_client_config=self.alipay_client_config,logger=logger)def trade_page(self,out_trade_no,total_amount,subject,body,product_code):"""页面接口示例:alipay.trade.page.pay「选择需要的接口」"""# 对照接口文档,构造请求对象model = AlipayTradePagePayModel()# 请求参数:必选# model.out_trade_no = "pay202406051355202"# model.total_amount = 50# model.subject = "测试"# model.body = "支付宝测试"# model.product_code = "FAST_INSTANT_TRADE_PAY"model.out_trade_no = out_trade_nomodel.total_amount = total_amountmodel.subject = subjectmodel.body = bodymodel.product_code = product_code# 请求参数:可选# settle_detail_info = SettleDetailInfo()# settle_detail_info.amount = 50# settle_detail_info.trans_in_type = "userId"# settle_detail_info.trans_in = "2088302300165604"# settle_detail_infos = list()# settle_detail_infos.append(settle_detail_info)# settle_info = SettleInfo()# settle_info.settle_detail_infos = settle_detail_infos# model.settle_info = settle_info# sub_merchant = SubMerchant()# sub_merchant.merchant_id = "2088301300153242"# model.sub_merchant = sub_merchant# 构造的请求request = AlipayTradePagePayRequest(biz_model=model)request.notify_url = settings.ALIPAY_NOTIFY_URL# request.return_url = settings.ALIPAY_RETURN_URL# request.notify_url = settings.ALIPAY_RETURN_URL# request.return_url = settings.ALIPAY_NOTIFY_URL# 得到构造的请求,如果http_method是GET,则是一个带完成请求参数的url,如果http_method是POST,则是一段HTML表单片段response = self.client.page_execute(request, http_method="GET")# 输出# print("")# print("==========================================")# print("alipay.trade.page.pay response:" + response)# 返回阶段return response# ))))))))) 结束# ========================================# 结束
最后,配置通知路由的对应视图:
from django.shortcuts import renderfrom django.utils import timezonefrom django_filters import rest_framework as filtersfrom django.conf import settingsfrom datetime import datetimefrom rest_framework import viewsets,statusfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework import paginationfrom trade.models import *from trade.serializers import *from trade.permissions import *from account.models import *from utils.error import *from utils.common import *from utils.payment_alipay import *# from utils.payment_alipay_new import *# Create your views here.# Cardclass CardFilter(filters.FilterSet):card_name = filters.CharFilter(lookup_expr='icontains')duration = filters.NumberFilter()class Meta:model = Cardfields = ['card_name', 'duration']class CardPagination(pagination.PageNumberPagination):page_size = 12max_page_size = 20page_size_query_param = 'page_size'class CardViewSet(viewsets.ModelViewSet):queryset = Card.objects.all()serializer_class = CardSerializerfilter_backends = (filters.DjangoFilterBackend,)filterset_class = CardFilterpagination_class = CardPagination# permission_classes = [# IsAdminUserOrReadonly,# ]# Orderclass OrderFilter(filters.FilterSet):pay_status = filters.CharFilter(lookup_expr='icontains')pay_type = filters.CharFilter(lookup_expr='icontains')class Meta:model = Orderfields = ['pay_status', 'pay_type']class OrderPagination(pagination.PageNumberPagination):page_size = 12max_page_size = 20page_size_query_param = 'page_size'class OrderViewset(viewsets.ModelViewSet):queryset = Order.objects.all()serializer_class = OrderSerializerfilter_backends = (filters.DjangoFilterBackend,)filterset_class = OrderFilterpagination_class = OrderPaginationpermission_classes = [IsAdminUserOrReadonly,]# Alipayclass AlipayAPIView(APIView):# 发起支付宝支付「场景」:# 1. 创建新的支付# 2. 支付未完成的订单def get(self, request):# 判断卡号「card_id」是否正确card_id = request.GET.get("card_id", None)print("================")print(card_id)try:card = Card.objects.get(id=card_id)except:return Response(response_data(*TradeError.CardParamError))# 订单号out_trade_no = "pay" + datetime.now().strftime("%Y%m%d%H%M%S") + get_random_code(4)# 创建订单try:Order.objects.create(user = Profile.objects.get(user=request.user),card = card,order_sn = out_trade_no,order_mount = card.card_price,pay_time = timezone.now(),)except:return Response(response_data(*TradeError.OrderCreateError))# 请求支付宝try:# pip install alipay-sdk-python --upgradealipay = Alipay()pay_url = alipay.trade_page(out_trade_no=out_trade_no,total_amount=str(card.card_price),subject=card.card_name,body="「支付宝 - 沙箱环境」支付",product_code="FAST_INSTANT_TRADE_PAY")# pip install python-alipay-sdk# 电脑网站支付 alipay.trade.page.pay# pay_url = alipay.api_alipay_trade_page_pay(# out_trade_no=out_trade_no,# total_amount=str(card.card_price),# subject=card.card_name,# return_url=settings.ALIPAY_RETURN_URL,# notify_url=settings.ALIPAY_NOTIFY_URL, # 可选,不填则使用默认 notify url# )# 返回return Response(pay_url)except:return Response(response_data(*TradeError.AlipayRequestError))class AlipayCallbackAPIView(APIView):def get(self, request):return Response("阿里支付 - 回调函数")def post(self, request):params = request.POST.dict()print(params)# 返回阶段return Response("success")
——————————————————
当你完成了上面的配置后,当你再次完成支付后,你的PyCharm的命令行就可以拿到支付宝反馈回来的「通知信息」:

{'gmt_create': '2024-06-06 00:30:07', 'charset': 'utf-8', 'gmt_payment': '2024-06-06 00:30:14', 'notify_time': '2024-06-06 00:30:15', 'subject': '黄金VIP', 'sign': '5yy89qKtqnhJVi8vuiRoxC5iUUJ0aNt3hX1l+0sDBe/vHCG6clZtxJqt7MysPwT7rjgo4gX7O7AQ48HuXjSzCsisjagkzdRafYZlB7X1Ph7P0sjetL9jyI8aNge4myBKmcpvflny6lnJZSEyM2fCVxPnFqWQ4YUyOgxJZStUr3wJZAOnJ3Dt4aXSRJAuuC0FQ+KW51L6nCTfwGrl9Z3IIndLtOrUP9xZa/92kKtJZGt1eh2V/YKZc2+DHD9C3Qj81LoYgEz6Zrt+gHZrsFRPv01ZgiIFY2+tPBDmyTutV2IvXSg0GL47BbS1/wHbzVrsM8MvgX9SPfqqjZp8V0YrVQ==', 'buyer_id': '2088722036954866', 'body': '「支付宝 - 沙箱环境」支付', 'invoice_amount': '148.00', 'version': '1.0', 'notify_id': '2024060601222003015154860503098223', 'fund_bill_list': '[{"amount":"148.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': 'pay202406060029514456', 'total_amount': '148.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2024060622001454860502950171', 'auth_app_id': '9021000137680887', 'receipt_amount': '148.00', 'point_amount': '0.00', 'buyer_pay_amount': '148.00', 'app_id': '9021000137680887', 'sign_type': 'RSA2', 'seller_id': '2088721036954856'}[06/Jun/2024 00:30:15] "POST api/callback/ HTTP/1.1" 200 9
11
支付宝「异步通知验签」
在上面的步骤中,当我们在支付宝的交易完成后,会通过「NOTIFY_URL」获得来自支付宝反馈的通知信息。
拿到了这个信息后,就可以进行「异步通知的验签」操作。
关于这一部分,官方的文档地址为:
https://opendocs.alipay.com/open/270/105902?pathHash=d5cd617e
在官方文档中,对异步返回结果的验签,一共有五步:

这一部分修改了自定义的支付宝的工具类,最新的完整代码如下所示:
「utils/payment_alipay.py」
#!/usr/bin/env python# -*- coding: UTF-8 -*-# ——————————————————# 脚本说明:# 支付方法:# 使用「支付宝」的「沙箱环境」# ——————————————————# ========================================# 开始# ))))))))) 模块包导入import base64import loggingimport traceback# 『单独运行』使用# 如果「settings」来自Django,需要社会环境变量# import os# import sys# sys.path.append('/Users/adamhuan/PycharmProjects/django_daily_media')# os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_daily_media.settings')# 「结合Django」使用import rsafrom django.conf import settingsfrom alipay.aop.api.AlipayClientConfig import AlipayClientConfigfrom alipay.aop.api.DefaultAlipayClient import DefaultAlipayClientfrom alipay.aop.api.FileItem import FileItemfrom alipay.aop.api.domain.AlipayTradeAppPayModel import AlipayTradeAppPayModelfrom alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModelfrom alipay.aop.api.domain.AlipayTradePayModel import AlipayTradePayModelfrom alipay.aop.api.domain.GoodsDetail import GoodsDetailfrom alipay.aop.api.domain.SettleDetailInfo import SettleDetailInfofrom alipay.aop.api.domain.SettleInfo import SettleInfofrom alipay.aop.api.domain.SubMerchant import SubMerchantfrom alipay.aop.api.request.AlipayOfflineMaterialImageUploadRequest import AlipayOfflineMaterialImageUploadRequestfrom alipay.aop.api.request.AlipayTradeAppPayRequest import AlipayTradeAppPayRequestfrom alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequestfrom alipay.aop.api.request.AlipayTradePayRequest import AlipayTradePayRequestfrom alipay.aop.api.response.AlipayOfflineMaterialImageUploadResponse import AlipayOfflineMaterialImageUploadResponsefrom alipay.aop.api.response.AlipayTradePayResponse import AlipayTradePayResponse# 通过 RSA 进行验签from alipay.aop.api.util.SignatureUtils import verify_with_rsa# ))))))))) 日志logging.basicConfig(level=logging.INFO,format='%(asctime)s %(levelname)s %(message)s',filemode='a',)logger = logging.getLogger('')# ))))))))) 类名# ))))))))) 执行阶段class Alipay():"""设置配置,包括支付宝网关地址、app_id、应用私钥、支付宝公钥等,其他配置值可以查看AlipayClientConfig的定义。"""alipay_client_config = AlipayClientConfig()# =================# Default# =================# 旧版本的API:openapi.alipay.com# 新版本的API:openapi-sandbox.dl.alipaydev.com# alipay_client_config.server_url = 'https://openapi.alipay.com/gateway.do'# alipay_client_config.app_id = '[your app_id]'# alipay_client_config.app_private_key = '[your app private key]'# alipay_client_config.alipay_public_key = '[alipay public key]'# =================# For Django Custom# =================alipay_client_config.server_url = settings.ALIPAY_SERVER_URLalipay_client_config.app_id = settings.ALIPAY_APP_IDalipay_client_config.app_private_key = settings.ALIPAY_APP_PRIVATE_KEYalipay_client_config.alipay_public_key = settings.ALIPAY_PUBLIC_KEY# alipay_client_config.notify_url = settings.ALIPAY_NOTIFY_URL# alipay_client_config.return_url = settings.ALIPAY_RETURN_URL# 输出# print("")# print("==========================================")# print(settings.ALIPAY_SERVER_URL)def __init__(self):"""得到客户端对象。注意,一个alipay_client_config对象对应一个DefaultAlipayClient,定义DefaultAlipayClient对象后,alipay_client_config不得修改,如果想使用不同的配置,请定义不同的DefaultAlipayClient。logger参数用于打印日志,不传则不打印,建议传递。"""self.client = DefaultAlipayClient(alipay_client_config=self.alipay_client_config,logger=logger)def trade_page(self,out_trade_no,total_amount,subject,body,product_code):"""页面接口示例:alipay.trade.page.pay「选择需要的接口」"""# 对照接口文档,构造请求对象model = AlipayTradePagePayModel()# 请求参数:必选# model.out_trade_no = "pay202406051355202"# model.total_amount = 50# model.subject = "测试"# model.body = "支付宝测试"# model.product_code = "FAST_INSTANT_TRADE_PAY"model.out_trade_no = out_trade_nomodel.total_amount = total_amountmodel.subject = subjectmodel.body = bodymodel.product_code = product_code# 请求参数:可选# settle_detail_info = SettleDetailInfo()# settle_detail_info.amount = 50# settle_detail_info.trans_in_type = "userId"# settle_detail_info.trans_in = "2088302300165604"# settle_detail_infos = list()# settle_detail_infos.append(settle_detail_info)# settle_info = SettleInfo()# settle_info.settle_detail_infos = settle_detail_infos# model.settle_info = settle_info# sub_merchant = SubMerchant()# sub_merchant.merchant_id = "2088301300153242"# model.sub_merchant = sub_merchant# 构造的请求request = AlipayTradePagePayRequest(biz_model=model)request.notify_url = settings.ALIPAY_NOTIFY_URL# request.return_url = settings.ALIPAY_RETURN_URL# request.notify_url = settings.ALIPAY_RETURN_URL# request.return_url = settings.ALIPAY_NOTIFY_URL# 得到构造的请求,如果http_method是GET,则是一个带完成请求参数的url,如果http_method是POST,则是一段HTML表单片段response = self.client.page_execute(request, http_method="GET")# 输出# print("")# print("==========================================")# print("alipay.trade.page.pay response:" + response)# 返回阶段return responsedef verify_sign(self, unsigned_string, sign):""":param unsigned_string: 需要验签的内容:param sign: sign参数:return: True - 验签成功 False - 验签失败"""return verify_with_rsa(settings.ALIPAY_PUBLIC_KEY,bytes(unsigned_string, encoding='utf-8'),sign)# ))))))))) 结束# ========================================# 结束
视图「trade/views.py」
from django.shortcuts import renderfrom django.utils import timezonefrom django_filters import rest_framework as filtersfrom django.conf import settingsfrom datetime import datetimefrom rest_framework import viewsets,statusfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework import paginationfrom trade.models import *from trade.serializers import *from trade.permissions import *from account.models import *from utils.error import *from utils.common import *from utils.payment_alipay import *# from utils.payment_alipay_new import *# Create your views here.# Cardclass CardFilter(filters.FilterSet):card_name = filters.CharFilter(lookup_expr='icontains')duration = filters.NumberFilter()class Meta:model = Cardfields = ['card_name', 'duration']class CardPagination(pagination.PageNumberPagination):page_size = 12max_page_size = 20page_size_query_param = 'page_size'class CardViewSet(viewsets.ModelViewSet):queryset = Card.objects.all()serializer_class = CardSerializerfilter_backends = (filters.DjangoFilterBackend,)filterset_class = CardFilterpagination_class = CardPagination# permission_classes = [# IsAdminUserOrReadonly,# ]# Orderclass OrderFilter(filters.FilterSet):pay_status = filters.CharFilter(lookup_expr='icontains')pay_type = filters.CharFilter(lookup_expr='icontains')class Meta:model = Orderfields = ['pay_status', 'pay_type']class OrderPagination(pagination.PageNumberPagination):page_size = 12max_page_size = 20page_size_query_param = 'page_size'class OrderViewset(viewsets.ModelViewSet):queryset = Order.objects.all()serializer_class = OrderSerializerfilter_backends = (filters.DjangoFilterBackend,)filterset_class = OrderFilterpagination_class = OrderPaginationpermission_classes = [IsAdminUserOrReadonly,]# Alipayclass AlipayAPIView(APIView):# 发起支付宝支付「场景」:# 1. 创建新的支付# 2. 支付未完成的订单def get(self, request):# 判断卡号「card_id」是否正确card_id = request.GET.get("card_id", None)print("================")print(card_id)try:card = Card.objects.get(id=card_id)except:return Response(response_data(*TradeError.CardParamError))# 订单号out_trade_no = "pay" + datetime.now().strftime("%Y%m%d%H%M%S") + get_random_code(4)# 创建订单try:Order.objects.create(user = Profile.objects.get(user=request.user),card = card,order_sn = out_trade_no,order_mount = card.card_price,pay_time = timezone.now(),)except:return Response(response_data(*TradeError.OrderCreateError))# 请求支付宝try:# pip install alipay-sdk-python --upgradealipay = Alipay()pay_url = alipay.trade_page(out_trade_no=out_trade_no,total_amount=str(card.card_price),subject=card.card_name,body="「支付宝 - 沙箱环境」支付",product_code="FAST_INSTANT_TRADE_PAY")# pip install python-alipay-sdk# 电脑网站支付 alipay.trade.page.pay# pay_url = alipay.api_alipay_trade_page_pay(# out_trade_no=out_trade_no,# total_amount=str(card.card_price),# subject=card.card_name,# return_url=settings.ALIPAY_RETURN_URL,# notify_url=settings.ALIPAY_NOTIFY_URL, # 可选,不填则使用默认 notify url# )# 返回return Response(pay_url)except:return Response(response_data(*TradeError.AlipayRequestError))class AlipayCallbackAPIView(APIView):def get(self, request):return Response("阿里支付 - 回调函数")def post(self, request):# =======================# 拿到异步返回的结果params = request.POST.dict()print(params)# =======================# 对异步返回结果的验签# ----------------# Step 1 - 在通知返回参数列表中,除去 sign、sign_type 两个参数外,凡是通知返回回来的参数皆是待验签的参数。# --- SIGN# del params['sign']sign = params.pop('sign')# --- SIGN_TYPEdel params['sign_type']# ----------------# Step 2 - 将剩下参数进行 url_decode,然后进行字典排序,组成字符串,得到待签名字符串# 对字典进行排序# 1. 列表生成式sorted_list = sorted([(k,v) for k,v in params.items() ])# 拼接字符串unsigned_string = '&'.join(f"{k}={v}" for k,v in sorted_list)# ----------------# Step 3 - 将签名参数(sign)使用 base64 解码为字节码串# Step 4 - 使用 RSA 的验签方法,通过签名字符串、签名参数(经过 base64 解码)及支付宝公钥验证签名# ALIPAY 的官方 PIP 包有提供方法解决「Step 3 Step 4」# from alipay.aop.api.util.SignatureUtils import verify_with_rsa# 该方法在单独实现的 ALIPAY 的工具类中有实现alipay = Alipay()if not alipay.verify_sign(unsigned_string=unsigned_string,sign=sign):print("==================")print("验签失败")return Response('验签失败')# ----------------# Step 5 - 需要严格按照如下描述校验通知数据的正确性# 1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。# 2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。# 3. 校验通知中的 seller_id(或者 seller_email)是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个 seller_id/seller_email)。# 4. 验证 app_id 是否为该商家本身。# ----------------# 上述 1、2、3、4 有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商家必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。# ----------------try:order = Order.objects.get(order_sn=params.get('out_trade_no'))except:return Response('「订单」不存在')if params.get('total_amount') != str(order.order_mount):print("==================")print("验签失败")return Response('验签失败')if params.get('seller_id') != settings.ALIPAY_SELLER_PID:print("==================")print("验签失败")return Response('验签失败')if params.get('app_id') != settings.ALIPAY_APP_ID:print("==================")print("验签失败")return Response('验签失败')if params.get('trade_status') not in ['TRADE_SUCCESS','TRADE_FINISHED']:print("==================")print("验签失败")return Response('验签失败')# 至此,验签「全部通过」# 执行其他的业务逻辑print("==================")print("验签成功")return Response("success")
——————————————————
当完成了上述配置之后,再次完成支付宝的支付后,就可以对支付宝返回的结果进行验签了:

{'gmt_create': '2024-06-06 01:47:09', 'charset': 'utf-8', 'gmt_payment': '2024-06-06 01:47:25', 'notify_time': '2024-06-06 01:47:27', 'subject': '黄金VIP', 'sign': 'kglWIvbDUeGINU5y4cZnE/1zOQ5JMMexVFrzISlcfuhfQqPnyu3FNgZzRo7SABlwBcH6mXm1lOFKWnl7e+lJIv8MIZa2TEG1a9nIFiMmJp2fXOB3W+KjQYMUcAxnktgkzskeriAhc057GJNqE7c8hNU718YfcrmMcveOqYcncJy0U9aKT/08s/8JRxKwywG/GnOwnl6/Na+t1ZBM3b3zmNVRz4mwXgtXah++jJOQvKrCNOefh6l5fnt3w15TOzBcl5rH5420KP3+WcQl0CTCMSuV/d7V7ftkzrs0Vj2XJS3zh/ggj5x+Qz0jJxV4jvYywYAE/cyY6SN1pK994HVqyA==', 'buyer_id': '2088722036954866', 'body': '「支付宝 - 沙箱环境」支付', 'invoice_amount': '148.00', 'version': '1.0', 'notify_id': '2024060601222014726054860503095122', 'fund_bill_list': '[{"amount":"148.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': 'pay202406060147015230', 'total_amount': '148.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2024060622001454860502959606', 'auth_app_id': '9021000137680887', 'receipt_amount': '148.00', 'point_amount': '0.00', 'buyer_pay_amount': '148.00', 'app_id': '9021000137680887', 'sign_type': 'RSA2', 'seller_id': '2088721036954856'}==================验签成功[06/Jun/2024 01:47:27] "POST api/callback/ HTTP/1.1" 200 9
至此,当支付宝的交易完成后的验签操作就全部完成了。
接下来,就是支付交易完成后的业务逻辑的处理了。
12
支付后「业务逻辑」
在支付宝支付成功后,业务逻辑的处理也是在一样的视图中进行处理的。
添加了业务逻辑后,完整的视图代码如下所示:
文件「trade/views.py」:
from django.shortcuts import renderfrom django.utils import timezonefrom django_filters import rest_framework as filtersfrom django.conf import settingsfrom django.db import transactionfrom datetime import datetime, timedeltafrom rest_framework import viewsets,statusfrom rest_framework.views import APIViewfrom rest_framework.response import Responsefrom rest_framework import paginationfrom trade.models import *from trade.serializers import *from trade.permissions import *from account.models import *from utils.error import *from utils.common import *from utils.payment_alipay import *# from utils.payment_alipay_new import *# Create your views here.# Cardclass CardFilter(filters.FilterSet):card_name = filters.CharFilter(lookup_expr='icontains')duration = filters.NumberFilter()class Meta:model = Cardfields = ['card_name', 'duration']class CardPagination(pagination.PageNumberPagination):page_size = 12max_page_size = 20page_size_query_param = 'page_size'class CardViewSet(viewsets.ModelViewSet):queryset = Card.objects.all()serializer_class = CardSerializerfilter_backends = (filters.DjangoFilterBackend,)filterset_class = CardFilterpagination_class = CardPagination# permission_classes = [# IsAdminUserOrReadonly,# ]# Orderclass OrderFilter(filters.FilterSet):pay_status = filters.CharFilter(lookup_expr='icontains')pay_type = filters.CharFilter(lookup_expr='icontains')class Meta:model = Orderfields = ['pay_status', 'pay_type']class OrderPagination(pagination.PageNumberPagination):page_size = 12max_page_size = 20page_size_query_param = 'page_size'class OrderViewset(viewsets.ModelViewSet):queryset = Order.objects.all()serializer_class = OrderSerializerfilter_backends = (filters.DjangoFilterBackend,)filterset_class = OrderFilterpagination_class = OrderPaginationpermission_classes = [IsAdminUserOrReadonly,]# Alipayclass AlipayAPIView(APIView):# 发起支付宝支付「场景」:# 1. 创建新的支付# 2. 支付未完成的订单def get(self, request):# 判断卡号「card_id」是否正确card_id = request.GET.get("card_id", None)print("================")print(card_id)try:card = Card.objects.get(id=card_id)except:return Response(response_data(*TradeError.CardParamError))# 订单号out_trade_no = "pay" + datetime.now().strftime("%Y%m%d%H%M%S") + get_random_code(4)# 创建订单try:Order.objects.create(user = Profile.objects.get(user=request.user),card = card,order_sn = out_trade_no,order_mount = card.card_price,pay_time = timezone.now(),)except:return Response(response_data(*TradeError.OrderCreateError))# 请求支付宝try:# pip install alipay-sdk-python --upgradealipay = Alipay()pay_url = alipay.trade_page(out_trade_no=out_trade_no,total_amount=str(card.card_price),subject=card.card_name,body="「支付宝 - 沙箱环境」支付",product_code="FAST_INSTANT_TRADE_PAY")# pip install python-alipay-sdk# 电脑网站支付 alipay.trade.page.pay# pay_url = alipay.api_alipay_trade_page_pay(# out_trade_no=out_trade_no,# total_amount=str(card.card_price),# subject=card.card_name,# return_url=settings.ALIPAY_RETURN_URL,# notify_url=settings.ALIPAY_NOTIFY_URL, # 可选,不填则使用默认 notify url# )# 返回return Response(pay_url)except:return Response(response_data(*TradeError.AlipayRequestError))class AlipayCallbackAPIView(APIView):def get(self, request):return Response("阿里支付 - 回调函数")def post(self, request):# =======================# 拿到异步返回的结果params = request.POST.dict()print(params)# =======================# 对异步返回结果的验签# ----------------# Step 1 - 在通知返回参数列表中,除去 sign、sign_type 两个参数外,凡是通知返回回来的参数皆是待验签的参数。# --- SIGN# del params['sign']sign = params.pop('sign')# --- SIGN_TYPEdel params['sign_type']# ----------------# Step 2 - 将剩下参数进行 url_decode,然后进行字典排序,组成字符串,得到待签名字符串# 对字典进行排序# 1. 列表生成式sorted_list = sorted([(k,v) for k,v in params.items() ])# 拼接字符串unsigned_string = '&'.join(f"{k}={v}" for k,v in sorted_list)# ----------------# Step 3 - 将签名参数(sign)使用 base64 解码为字节码串# Step 4 - 使用 RSA 的验签方法,通过签名字符串、签名参数(经过 base64 解码)及支付宝公钥验证签名# ALIPAY 的官方 PIP 包有提供方法解决「Step 3 Step 4」# from alipay.aop.api.util.SignatureUtils import verify_with_rsa# 该方法在单独实现的 ALIPAY 的工具类中有实现alipay = Alipay()if not alipay.verify_sign(unsigned_string=unsigned_string,sign=sign):print("==================")print("验签失败")return Response('验签失败')# ----------------# Step 5 - 需要严格按照如下描述校验通知数据的正确性# 1. 商家需要验证该通知数据中的 out_trade_no 是否为商家系统中创建的订单号。# 2. 判断 total_amount 是否确实为该订单的实际金额(即商家订单创建时的金额)。# 3. 校验通知中的 seller_id(或者 seller_email)是否为 out_trade_no 这笔单据的对应的操作方(有的时候,一个商家可能有多个 seller_id/seller_email)。# 4. 验证 app_id 是否为该商家本身。# ----------------# 上述 1、2、3、4 有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商家必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。# ----------------try:order = Order.objects.get(order_sn=params.get('out_trade_no'))except:return Response('「订单」不存在')if params.get('total_amount') != str(order.order_mount):print("==================")print("验签失败")return Response('验签失败')if params.get('seller_id') != settings.ALIPAY_SELLER_PID:print("==================")print("验签失败")return Response('验签失败')if params.get('app_id') != settings.ALIPAY_APP_ID:print("==================")print("验签失败")return Response('验签失败')if params.get('trade_status') not in ['TRADE_SUCCESS','TRADE_FINISHED']:print("==================")print("验签失败")return Response('验签失败')# 至此,验签「全部通过」# 执行其他的业务逻辑# 数据库「事务一致性」with transaction.atomic():# ======================# --- 修改 ORDER 表# ======================order.trade_no = params.get('trade_no')order.pay_status = params.get('trade_status')# order.pay_time = datetime.now()order.pay_time = timezone.now()# 保存修改order.save()# ======================# --- 修改 PROFILE 表# ======================profile = Profile.objects.get(uid=order.user.uid)profile.is_upgrade = 1# profile.upgrade_time = datetime.now()profile.upgrade_time = timezone.now()profile.upgrade_count += 1# 如果首次充值或者会员已过期# 如果会员未过期 - 在原来的基础时间上再次加上时长if not profile.expire_time or profile.expire_time < timezone.now():# profile.expire_time = datetime.now() + timedelta(days=order.card.duration)profile.expire_time = timezone.now() + timedelta(days=order.card.duration)else:profile.expire_time = profile.expire_time + timedelta(days=order.card.duration)# 保存修改profile.save()print("==================")print("验签成功")return Response("success")
13
终了
至此,关于「支付宝」的「沙箱环境」的「Python + Vue.JS」相关的完整使用,基本上就介绍完了。
如果你对本文所关联的项目感兴趣,它的「前端与后端」完整代码在我的GITEE仓库:
后端Django:
https://gitee.com/leviathan-litan/django_daily_media
前端Vue.JS:
https://gitee.com/leviathan-litan/django_daily_media_frontend
END
温馨提示
如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注我。




