您现在的位置是: 网站首页 >Django >Vue+Django REST framework前后端分离生鲜电商 Django

【Vue+DRF生鲜电商】28.支付宝支付接口类解读,支付逻辑编辑

admin2019年8月10日 21:13 Django | Python | Vue 1547人已围观

Vue+Django REST framework前后端分离生鲜电商简介 Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。 Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ; Django版本:2.2、djangorestframework:3.9.2。 前端Vue模板可以直接联系我拿。

## 支付接口类alipay.py解读 ### 下载 alipay.py 源码 访问 https://github.com/liyaopinner/mxshop_sources 下载 “慕学生鲜电商的部分资源文件- alipay.py ” 复制内容放在 utils/alipay.py 文件中,对其进行修改,改为自己项目所需的 ```python # -*- coding: utf-8 -*- # pip install pycryptodome __author__ = 'bobby' from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from base64 import b64encode, b64decode from urllib.parse import quote_plus from urllib.parse import urlparse, parse_qs from urllib.request import urlopen from base64 import decodebytes, encodebytes import json class AliPay(object): """ 支付宝支付接口 """ def __init__(self, app_id, notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=True): self.app_id = app_id # 支付宝分配的应用ID self.notify_url = notify_url # 支付宝服务器主动通知商户服务器里指定的页面http/https路径;用户一旦支付,会向该url发一个异步的请求给自己服务器,这个一定需要公网可访问 self.app_private_key_path = app_private_key_path # 个人私钥路径 self.app_private_key = None # 个人私钥内容 self.return_url = return_url # 网页上支付完成后跳转回自己服务器的url with open(self.app_private_key_path) as fp: # 读取个人私钥文件提取到私钥内容 self.app_private_key = RSA.importKey(fp.read()) self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: # 读取支付宝公钥文件提取公钥内容,支付宝公钥在代码中验签使用 self.alipay_public_key = RSA.import_key(fp.read()) if debug is True: # 使用沙箱的网关 self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): biz_content = { # 请求参数的集合 "subject": subject, # 订单标题 "out_trade_no": out_trade_no, # 商户订单号, "total_amount": total_amount, # 订单总金额 "product_code": "FAST_INSTANT_TRADE_PAY", # 销售产品码,默认 # "qr_pay_mode":4 } biz_content.update(kwargs) # 合并其他请求参数字典 data = self.build_body("alipay.trade.page.pay", biz_content, return_url) # 将请求参数合并到公共参数字典的键biz_content中 return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): """ 组合所有的请求参数到一个字典中 :param method: :param biz_content: :param return_url: :return: """ data = { "app_id": self.app_id, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is None: data["notify_url"] = self.notify_url data["return_url"] = self.return_url return data def ordered_data(self, data): """ 并按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。 :param data: :return: 返回的是数组列表,按照数据中的k进行排序的 """ complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 将字典类型的数据dump出来 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): """ 使用各自语言对应的SHA256WithRSA(对应sign_type为RSA2)或SHA1WithRSA(对应sign_type为RSA)签名函数利用商户私钥对待签名字符串进行签名,并进行Base64编码。 :param unsigned_string: :return: """ # 开始计算签名 key = self.app_private_key signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA256.new(unsigned_string)) # base64 编码,转换为unicode表示并移除回车 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def sign_data(self, data): """ 获取所有请求参数,不包括字节类型参数,如文件、字节流,剔除sign字段,剔除值为空的参数。 进行排序。 将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。 然后对该字符串进行签名。 把生成的签名赋值给sign参数,拼接到请求参数中。 :param data: :return: """ data.pop("sign", None) # 排序后的字符串 ordered_items = self.ordered_data(data) # 数组列表,进行遍历拼接 unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in ordered_items) # 使用参数=值得格式用&连接 sign = self.sign(unsigned_string.encode("utf-8")) # 得到签名后的字符串 quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in ordered_items) # quote_plus给url进行预处理,特殊字符串在url中会有问题 # 获得最终的订单信息字符串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string def _verify(self, raw_content, signature): # 开始计算签名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序后的字符串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature) ``` 因为要用到加密,要用到Python中的 `pip install pycryptodome`,当前安装的版本为 3.8.2 ### 项目配置服务器IP 在项目下创建 ProjectConfig.ini 配置文件,该配置文件主要是用来记录项目中涉密的配置,比如连接数据库帐密,服务器信息等。结构如下 ```ini [DjangoOnlineFreshSupermarket] server_ip=xx.ip.ip.xx ``` 接下来在 utils/alipay.py 中添加一个函数,用于获取这个服务器IP ```python def get_server_ip(): """ 在项目根目录项创建ProjectConfig.ini配置文件,读取其中配置的IP地址 :return: """ import configparser import os import sys # 获取当前文件的路径(运行脚本) pwd = os.path.dirname(os.path.realpath(__file__)) # 获取项目的根目录 sys.path.append(pwd + "../") # 要想单独使用django的model,必须指定一个环境变量,会去settings配置找 # 参照manage.py里面就知道为什么这样设置了 os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DjangoOnlineFreshSupermarket.settings') import django django.setup() from django.conf import settings config = configparser.ConfigParser() config.read(os.path.join(settings.BASE_DIR, 'ProjectConfig.ini')) server_ip = config['DjangoOnlineFreshSupermarket']['server_ip'] return server_ip ``` 可以在 utils/alipay.py 中添加一个 mian函数,测试下能够正确获取 ```python if __name__ == "__main__": print(get_server_ip()) server_ip = get_server_ip() # 得到自己服务器的IP地址,也就是之前同步项目上去的 ``` ### 测试订单创建支付 在 main 函数中添加订单支付测试 ```python if __name__ == "__main__": print(get_server_ip()) server_ip = get_server_ip() # 得到自己服务器的IP地址,也就是之前同步项目上去的 alipay = AliPay( app_id="2016100900646609", # 自己支付宝沙箱 APP ID notify_url="http://{}:8000/".format(server_ip), app_private_key_path="../apps/trade/keys/private_key_2048.txt", # 可以使用相对路径那个 alipay_public_key_path="../apps/trade/keys/alipay_key_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://{}:8000/".format(server_ip) ) # 创建订单 url = alipay.direct_pay( subject="测试订单", out_trade_no="2019080716060001", total_amount=0.01 ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) print(re_url) ``` 直接运行即可 这时候会得到一个链接 ```url https://openapi.alipaydev.com/gateway.do?app_id=2016100900646609&biz_content=%7B%22subject%22%3A%22%5Cu6d4b%5Cu8bd5%5Cu8ba2%5Cu5355%22%2C%22out_trade_no%22%3A%222019080716060001%22%2C%22total_amount%22%3A0.01%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay&notify_url=http%3A%2F%2Fxx.ip.ip.xx%3A8000%2F&return_url=http%3A%2F%2Fxx.ip.ip.xx%3A8000%2F&sign_type=RSA2&timestamp=2019-08-07+16%3A09%3A25&version=1.0&sign=crNYPmSRAccnEb%2BnvnYqgG6qpp4n5NrOHP4sBLyjNBWws6RWS5JrGntGX%2FG2SGqf21dIwvUtt5sV5XY%2Bol1dId%2Bn%2BVBykzJShjB4Y0mt%2Bgm498Tv5ecUCUFvOFXY%2BpWRu3HiuuiJXxCHHzEZ795sw1x8xSQaKZCTEHCBZsfwexKwE1UKsCWLv1cfgjO3O8rCMziSASTMta%2BlfmPcZTdO9tTI9qTXE%2Bq2TMQpZWqBZvN1LPKHdzv1TZL3efjI64qEKglYK6KUCtUgNJoUBJrmYj4Ao3XZMro06Lu73MTPpheg8v56yBXGe4FyMdpxOvOS2t%2FnRZtyM2cx5io6lUoScQ%3D%3D ``` 浏览器访问该链接可以看到沙箱支付页面 ![BLOG_20190810_211542_27](/media/blog/images/2019/08/BLOG_20190810_211542_27.png "博客图集BLOG_20190810_211542_27.png") 可以使用登录账户付款(在开放平台-沙箱环境-沙箱账号中),也可以下载支付宝沙箱测试应用进行扫码支付。 ![BLOG_20190810_211536_42](/media/blog/images/2019/08/BLOG_20190810_211536_42.png "博客图集BLOG_20190810_211536_42.png") 支付完成后就会停留在该界面,如果想要跳回商户界面,需要配置`return_url` ```python if __name__ == "__main__": print(get_server_ip()) server_ip = get_server_ip() # 得到自己服务器的IP地址,也就是之前同步项目上去的 alipay = AliPay( app_id="2016100900646609", # 自己支付宝沙箱 APP ID notify_url="http://{}:8000/".format(server_ip), app_private_key_path="../apps/trade/keys/private_key_2048.txt", # 可以使用相对路径那个 alipay_public_key_path="../apps/trade/keys/alipay_key_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://{}:8000/".format(server_ip) ) # 创建订单 url = alipay.direct_pay( subject="测试订单", out_trade_no="2019080716060003", total_amount=0.01, return_url="http://{}:8000/".format(server_ip) # 支付完成后自动跳回该url ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) print(re_url) ``` 现在修改订单号测试,如果不修改,支付宝会提示该订单已支付。 ![BLOG_20190810_211527_42](/media/blog/images/2019/08/BLOG_20190810_211527_42.png "博客图集BLOG_20190810_211527_42.png") 支付完成会自动跳转到`return_url="http://{}:8000/".format(server_ip)`配置的地址,并且传入很多参数 ```url http://xx.ip.ip.xx:8000/?charset=utf-8&out_trade_no=2019080716060003&method=alipay.trade.page.pay.return&total_amount=0.01&sign=Ej1B3IHs4q6aaWXLqwphXelaPcrok%2Ft7FPmFwns368fc6LcDfvLgBFAqsVwryWMJmHddwiiGgDDSkJKl0f30dYyS0mh9HXlbAXacUJgHHRUvsrN9BiJ9GBoLf%2FInKXcC6n7bqkSDORW1Rnn2N1354NieHVW9W67dNhHCN%2FnpVzALX2c%2F5g7z06i1aYIdLVDfUsXVzn2fDGhZ%2BAHYoFoDKMTr0kFuPGSNPnt8%2F0Q75je8%2BbWmF%2BXMOm1152wVHUMyL1LEO9n9NDba1IPTLWBzlw11CM4eFIIZrTWheCKuACrrNAGDLDSMtDIKwsOWuGY8Z1DCODerGR8Mqbprx%2BSGLQ%3D%3D&trade_no=2019080722001421571000030866&auth_app_id=2016100900646609&version=1.0&app_id=2016100900646609&sign_type=RSA2&seller_id=2088102178762103&timestamp=2019-08-07+16%3A31%3A37 ``` ![BLOG_20190810_211521_10](/media/blog/images/2019/08/BLOG_20190810_211521_10.png "博客图集BLOG_20190810_211521_10.png") ### 分析return_url和notify_url 当用户创建好订单之后,跳转到支付宝页面进行支付,如果用户通过扫码或登录支付宝,就会创建一个支付宝订单,确认支付后,支付宝会自动跳转到`return_url`配置的商户页面,我们可以获取到url中的参数,来验证用户是否已经支付,如果支付就修改订单状态。 还有一种情况就是创建订单,通过扫码,或登录支付宝创建支付宝订单后,用户并没有确认支付,而是关闭了该页面,在手机上个人订单中去支付,那么`return_url`就无效了,而此时,我们的应用就无法判断该订单是否已支付。这时候`notify_url`就有用了,支付宝会通过异步方式,向该url发起一个请求(POST),并传递一些参数,我们的应用获取参数,解析其中的信息,对订单状态进行修改。 ### 验证支付宝返回的数据 用户支付后,会跳回`return_url`,我们需要对url进行验证,判定用户是否确认时已支付的,因为数据在传输过程中可能会被截获修改,如果不去做验证,系统上修改订单状态后,却没收到款。 > 获取url中所有参数,通过支付宝公钥解密 ```python if __name__ == "__main__": print(get_server_ip()) server_ip = get_server_ip() # 得到自己服务器的IP地址,也就是之前同步项目上去的 alipay = AliPay( app_id="2016100900646609", # 自己支付宝沙箱 APP ID notify_url="http://{}:8000/".format(server_ip), app_private_key_path="../apps/trade/keys/private_key_2048.txt", # 可以使用相对路径那个 alipay_public_key_path="../apps/trade/keys/alipay_key_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://{}:8000/".format(server_ip) ) # 创建订单 # url = alipay.direct_pay( # subject="测试订单", # out_trade_no="2019080716060003", # total_amount=0.01, # return_url="http://{}:8000/".format(server_ip) # 支付完成后自动跳回该url # ) # re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) # print(re_url) # 支付成功后跳回的页面url return_url = "http://xx.ip.ip.xx:8000/?charset=utf-8&out_trade_no=2019080716060003&method=alipay.trade.page.pay.return&total_amount=0.01&sign=Ej1B3IHs4q6aaWXLqwphXelaPcrok%2Ft7FPmFwns368fc6LcDfvLgBFAqsVwryWMJmHddwiiGgDDSkJKl0f30dYyS0mh9HXlbAXacUJgHHRUvsrN9BiJ9GBoLf%2FInKXcC6n7bqkSDORW1Rnn2N1354NieHVW9W67dNhHCN%2FnpVzALX2c%2F5g7z06i1aYIdLVDfUsXVzn2fDGhZ%2BAHYoFoDKMTr0kFuPGSNPnt8%2F0Q75je8%2BbWmF%2BXMOm1152wVHUMyL1LEO9n9NDba1IPTLWBzlw11CM4eFIIZrTWheCKuACrrNAGDLDSMtDIKwsOWuGY8Z1DCODerGR8Mqbprx%2BSGLQ%3D%3D&trade_no=2019080722001421571000030866&auth_app_id=2016100900646609&version=1.0&app_id=2016100900646609&sign_type=RSA2&seller_id=2088102178762103&timestamp=2019-08-07+16%3A31%3A37" # 根据返回的链接,测试验证签名 o = urlparse(return_url) query = parse_qs(o.query) # 获取url返回的签名sign ali_sign = query.pop("sign")[0] processed_query = {} for key, value in query.items(): print('{}:{}'.format(key, value)) processed_query[key] = value[0] # 可以提取到响应参数值 """ charset:['utf-8'] out_trade_no:['2019080716060003'] method:['alipay.trade.page.pay.return'] total_amount:['0.01'] trade_no:['2019080722001421571000030866'] auth_app_id:['2016100900646609'] version:['1.0'] app_id:['2016100900646609'] sign_type:['RSA2'] seller_id:['2088102178762103'] timestamp:['2019-08-07 16:31:37'] """ print(alipay.verify(processed_query, ali_sign)) ``` 最终如果返回为`True`表明确认用户已支付,否则返回`False`,如果我们修改支付宝公钥内容,或者返回的url参数有误,是不能验证成功的。 ## 支付宝支付逻辑编写 由于`return_url`时一个同步GET请求,`notify_url`是一个异步POST请求,可以将其放在一个API中实现,跟支付宝相关,没有model,所以就用最底层的`APIView`。 **对于`return_url`,用户支付完成后,支付宝会根据API中商户传入的`return_url`参数,通过GET请求的形式将部分支付结果参数通知到商户系统。** `notify_url`很重要,只要用户完成支付,就向该url发起一个异步请求,告诉用户已支付。 在 支付宝开放平台文档中心-电脑网站支付 中,支付结果异步通知 https://docs.open.alipay.com/270/105902/ 文档可以看到相关参数 **对于 PC 网站支付的交易,在用户支付完成之后,支付宝会根据 API 中商户传入的 `notify_url`,通过 POST 请求的形式将支付结果作为参数通知到商户系统。** 在 apps/trade/views.py 增加一个API视图,用于处理支付宝的返回 ```python from rest_framework.views import APIView class AliPayView(APIView): def get(self, request): """ 处理支付宝return_url返回 :param request: :return: """ pass def post(self, request): """ 处理支付宝notify_url异步通知 :param request: :return: """ pass ``` 在 DjangoOnlineFreshSupermarket/urls.py 添加该视图`AliPayView`的url ```python from trade.views import AliPayView urlpatterns = [ path('admin/', admin.site.urls), path('api-auth/', include('rest_framework.urls')), # drf 认证url path('api-token-auth/', views.obtain_auth_token), # drf token获取的url # path('api/token/', simplejwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'), # simplejwt认证接口 path('login/', simplejwt_views.TokenObtainPairView.as_view(), name='token_obtain_pair'), # 登录一般是login path('api/token/refresh/', simplejwt_views.TokenRefreshView.as_view(), name='token_refresh'), # simplejwt认证接口 path('ckeditor/', include('ckeditor_uploader.urls')), # 配置富文本编辑器url path('', include(router.urls)), # API url现在由路由器自动确定。 # DRF文档 path('docs/', include_docs_urls(title='DRF文档')), # 支付宝通知接口 path('alipay/return/', AliPayView.as_view(), name='alipay') ] ``` 这儿一定要使用`AliPayView.as_view()`,基于类的视图加上`.as_view()`。 ### 完成notify_url逻辑处理 编写该接口,用户处理支付宝支付后,异步通知接口。首先来确认下,支付宝完成支付后,是否有请求访问到该接口。 先修改 utils/alipay.py 将`return_url`和`notify_url`都配置成上面刚创建的url,然后修改下新的订单号 ```python if __name__ == "__main__": print(get_server_ip()) server_ip = get_server_ip() # 得到自己服务器的IP地址,也就是之前同步项目上去的 alipay = AliPay( app_id="2016100900646609", # 自己支付宝沙箱 APP ID notify_url="http://{}:8000/alipay/return/".format(server_ip), app_private_key_path="../apps/trade/keys/private_key_2048.txt", # 可以使用相对路径那个 alipay_public_key_path="../apps/trade/keys/alipay_key_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=True, # 默认False, return_url="http://{}:8000/alipay/return/".format(server_ip) ) # 创建订单 url = alipay.direct_pay( subject="测试订单", out_trade_no="2019080716060009", total_amount=0.01, return_url="http://{}:8000/alipay/return/".format(server_ip) # 支付完成后自动跳回该url ) re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) print(re_url) ``` **将所有代码同步到服务器上去**,如果不同步,启动的是服务器上未修改的代码,结果是不一样的,现在把所有代码全都upload to,点击项目选择后上传。 ![BLOG_20190810_211507_53](/media/blog/images/2019/08/BLOG_20190810_211507_53.png "博客图集BLOG_20190810_211507_53.png") 将运行配置设置为服务器的环境后 ![BLOG_20190810_211501_39](/media/blog/images/2019/08/BLOG_20190810_211501_39.png "博客图集BLOG_20190810_211501_39.png") 重新Debug启动,将`post`方法标记为断点 ![BLOG_20190810_211454_99](/media/blog/images/2019/08/BLOG_20190810_211454_99.png "博客图集BLOG_20190810_211454_99.png") 直接运行 utils/alipay.py ,会输出一个链接,在浏览器中访问该链接并进行支付,支付完成后,后台进入Debug,查看`request`的内容 ![BLOG_20190810_211449_59](/media/blog/images/2019/08/BLOG_20190810_211449_59.png "博客图集BLOG_20190810_211449_59.png") 拿到这个`sign`后,可以通过支付宝的公钥进行验签,验证是否是支付宝发过来的。 可以访问 https://docs.open.alipay.com/270/105902/ 查看参数 #### 异步通知参数-公共参数 | 参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 | | --- | --- | --- | --- | --- | --- | | notify_time | 通知时间 | Date | 是 | 通知的发送时间。格式为yyyy-MM-dd HH:mm:ss | 2015-14-27 15:45:58 | | notify_type | 通知类型 | String(64) | 是 | 通知的类型 | trade\_status\_sync | | notify_id | 通知校验ID | String(128) | 是 | 通知校验ID | ac05099524730693a8b330c5ecf72da978 | | charset | 编码格式 | String(10) | 是 | 编码格式,如utf-8、gbk、gb2312等 | utf-8 | | version | 接口版本 | String(3) | 是 | 调用的接口版本,固定为:1.0 | 1.0 | | sign_type | 签名类型 | String(10) | 是 | 签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 | RSA2 | | sign | 签名 | String(256) | 是 | 请参考[异步返回结果的验签](https://docs.open.alipay.com/#s7) | 601510b7970e52cc63db0f44997cf70e | | auth\_app\_id | 授权方的app_id | String(32) | 是 | 授权方的appid,由于本接口暂不开放第三方应用授权,因此auth\_app\_id=app_id | 2014072300007148 | 拿到 sign 签名之后,验证是否是支付宝发来的。 #### 异步通知参数-业务参数 | 参数 | 参数名称 | 类型 | 必填 | 描述 | 范例 | | -------------- | ------------- | ---------- | ---- | ------------------------------------------------------------------ | ---------------------------- | | trade_no | 支付宝交易号 | String(64) | 是 | 支付宝交易凭证号 | 2013112011001004330000121536 | | app_id | 开发者的app_id | String(32) | 是 | 支付宝分配给开发者的应用 ID | 2014072300007148 | | out\_trade\_no | 商户订单号 | String(64) | 是 | 原支付请求的商户订单号 | 6823789339978248 | | trade_status | 交易状态 | String(32) | 否 | 交易目前所处的状态,见[交易状态说明](https://docs.open.alipay.com/#s1) | TRADE_CLOSED | 业务参数中 out\_trade\_no 可以获取商户订单号,通过该订单号,可以将数据库该订单状态进行修改。 支付宝交易状态说明 | 枚举名称 | 枚举说明 | | ---------------- | ------------------------------------ | | WAIT\_BUYER\_PAY | 交易创建,等待买家付款 | | TRADE_CLOSED | 未付款交易超时关闭,或支付完成后全额退款 | | TRADE_SUCCESS | 交易支付成功 | | TRADE_FINISHED | 交易结束,不可退款 | #### 支付宝交易状态同步订单状态值 可以将这些状态定义到订单信息model中,修改 apps/trade/models.py 中的`OrderInfo` ```python class OrderInfo(models.Model): """ 订单 """ # ORDER_STATUS = ( # ('success', '成功'), # ('cancel', '取消'), # ('topaid', '待支付') # ) ORDER_STATUS = ( ('TRADE_FINISHED', '交易完成'), ('TRADE_SUCCESS', '支付成功'), ('WAIT_BUYER_PAY', '交易创建'), ('TRADE_CLOSE', '交易关闭') ) # 字段省略 pay_status = models.CharField(choices=ORDER_STATUS, default='WAIT_BUYER_PAY', max_length=20, verbose_name='订单状态', help_text='订单状态') # 字段省略 ``` #### Vue中同步订单状态值 在 src/views/member/order.vue 订单列表页面,进行以下修改 ```JavaScript <td v-if="item.pay_status == 'topaid' " align="center" bgcolor="#ffffff">待支付</td> <td v-if="item.pay_status == 'success' " align="center" bgcolor="#ffffff">已支付</td> ``` 修改为 ```JavaScript <td v-if="item.pay_status == 'WAIT_BUYER_PAY' " align="center" bgcolor="#ffffff">待支付</td> <td v-if="item.pay_status == 'TRADE_SUCCESS' " align="center" bgcolor="#ffffff">已支付</td> ``` 在 src/views/member/orderDetail.vue 订单详情页面也做同样的修改 ```JavaScript <td v-if="orderInfo.pay_status == 'topaid' " align="left" bgcolor="#ffffff">待支付&nbsp;&nbsp;&nbsp;&nbsp;<div style="text-align:center"><a :href="orderInfo.alipay_url"><input type="button" onclick="" value="立即使用支付宝支付"></a></div> </td> <td v-if="orderInfo.pay_status == 'success' " align="left" bgcolor="#ffffff">已支付</td> ``` 修改为 ```JavaScript <td v-if="orderInfo.pay_status == 'WAIT_BUYER_PAY' " align="left" bgcolor="#ffffff">待支付&nbsp;&nbsp;&nbsp;&nbsp;<div style="text-align:center"><a :href="orderInfo.alipay_url"><input type="button" onclick="" value="立即使用支付宝支付"></a></div> </td> <td v-if="orderInfo.pay_status == 'TRADE_SUCCESS' " align="left" bgcolor="#ffffff">已支付</td> ``` #### 支付宝参数配置保存在settings.py 可以将支付宝相关的配置保存在 DjangoOnlineFreshSupermarket/settings.py 文件中,其他文件直接调用即可,无需重复写多次,如果后面配置有变化,直接修改一个地方即可,比如从沙箱转移到正式环境后,只需要将`alipay_debug = False`即可 ```python # 支付宝相关配置 app_id = "2016100900646609" alipay_debug = True app_private_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/private_key_2048.txt') alipay_public_key_path = os.path.join(BASE_DIR, "apps/trade/keys/alipay_key_2048.txt") ``` #### notify_url验签处理订单状态 修改 apps/trade/views.py 中的`AliPayView` ```python from rest_framework.views import APIView from rest_framework.response import Response from utils.alipay import AliPay, get_server_ip from DjangoOnlineFreshSupermarket.settings import app_id, alipay_debug, alipay_public_key_path, app_private_key_path from django.utils import timezone class AliPayView(APIView): def get(self, request): """ 处理支付宝return_url返回 :param request: :return: """ pass def post(self, request): """ 处理支付宝notify_url异步通知 :param request: :return: """ processed_dict = {} for key, value in request.POST.items(): processed_dict[key] = value print('request.POST的值:', processed_dict) sign = processed_dict.pop('sign', None) # 直接就是字符串了 server_ip = get_server_ip() alipay = AliPay( app_id=app_id, # 自己支付宝沙箱 APP ID notify_url="http://{}:8000/alipay/return/".format(server_ip), app_private_key_path=app_private_key_path, # 可以使用相对路径那个 alipay_public_key_path=alipay_public_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=alipay_debug, # 默认False, return_url="http://{}:8000/alipay/return/".format(server_ip) ) verify_result = alipay.verify(processed_dict, sign) # 验证签名,如果成功返回True if verify_result: order_sn = processed_dict.get('out_trade_no') # 原支付请求的商户订单号 trade_no = processed_dict.get('trade_no') # 支付宝交易凭证号 trade_status = processed_dict.get('trade_status') # 交易目前所处的状态 # 更新数据库订单状态 OrderInfo.objects.filter(order_sn=order_sn).update( trade_no=trade_no, # 更改交易号 pay_status=trade_status, # 更改支付状态 pay_time=timezone.now() # 更改支付时间 ) # 给支付宝返回一个消息,证明已收到异步通知 # 当商户收到服务器异步通知并打印出 success 时,服务器异步通知参数 notify_id 才会失效。 # 也就是说在支付宝发送同一条异步通知时(包含商户并未成功打印出 success 导致支付宝重发数次通知),服务器异步通知参数 notify_id 是不变的。 return Response('success') ``` 当验证成功后`return Response('success')`,否则支付宝会重复发通知。 ### 完成return_url逻辑处理 对比下POST和GET中的内容 ```json [08/Aug/2019 13:25:45] "POST /orderinfo/ HTTP/1.1" 201 12954 request.POST的值: {'gmt_create': '2019-08-08 13:26:12', 'charset': 'utf-8', 'gmt_payment': '2019-08-08 13:26:20', 'notify_time': '2019-08-08 13:26:21', 'subject': '生鲜超市-20190808132544135', 'sign': 'DkQqkJwi5BnNKdlaVrybA0oLC9wL7aD61aV7vaF6jF6KrFsdDKAS+fS8Y2MQuKqAcPobsM/8ab3FVo7diRnEwXckugP8m6KW5TxFnnPwDh6J3dbNDUNyXTWYKiHCAKMvsV4ZuM7YkJQEGGbs2irf90uM8kxvOaZBGuI6D8QPhc/o6CyCYUJgJU4Zvs6DFuu9Wo1JwldnA9K4E9dd5UJpLI5KmIP6OTjZ13EcoXaslRgjcYdDAj21+cqCpwuD5+EGOuO+6/7T/WMgNSjPPy+cSehVBI9GnhuM7WmH2IQafR+510BLgN12agv4DJB+E1dZAibFAMJFA3Gn/cKDPPjqeQ==', 'buyer_id': '2088102179421571', 'invoice_amount': '10.00', 'version': '1.0', 'notify_id': '2019080800222132620021571000416125', 'fund_bill_list': '[{"amount":"10.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': '20190808132544135', 'total_amount': '10.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2019080822001421571000029372', 'auth_app_id': '2016100900646609', 'receipt_amount': '10.00', 'point_amount': '0.00', 'app_id': '2016100900646609', 'buyer_pay_amount': '10.00', 'sign_type': 'RSA2', 'seller_id': '2088102178762103'} [08/Aug/2019 13:26:22] "POST /alipay/return/ HTTP/1.1" 200 9 request.GET的值: {'charset': 'utf-8', 'out_trade_no': '20190808132544135', 'method': 'alipay.trade.page.pay.return', 'total_amount': '10.00', 'sign': 'FJputH1jTssknTDV1C5OUOkk3mFNK8qQj5EdszFrizxPmOvS5++v0Apl8ajggksgwlDk20yF/JwI3CdozpnErxZ/4pDMdU2I2A99vXb55akbVPJK6T9/YOo10HC/X+ctctm63ew/vRBIcxtP+BTgj7U+TvKwGkCdy0ZxRm1Ja9GJOwE8Nb4qdp0BQo4hFQ96QNVb0tQ9wTPe6R3qdjLdfyqHhj+GnILxBBehOSHJmDzsQMeMrKhOdY9FtXj21b8aW1YKNulhIU7C8TeUAifu3khmocNMwH+iv9A/hhlqrWjCvlCDUi1GDNRm1W9PCHl+vV6XIitetIOOOsmLlAfq2A==', 'trade_no': '2019080822001421571000029372', 'auth_app_id': '2016100900646609', 'version': '1.0', 'app_id': '2016100900646609', 'sign_type': 'RSA2', 'seller_id': '2088102178762103', 'timestamp': '2019-08-08 13:26:27'} ``` ```python class AliPayView(APIView): def get(self, request): """ 处理支付宝return_url返回 :param request: :return: """ processed_dict = {} for key, value in request.GET.items(): # GET逻辑和POST基本一样 processed_dict[key] = value print('request.GET的值:', processed_dict) sign = processed_dict.pop('sign', None) # 直接就是字符串了 server_ip = get_server_ip() alipay = AliPay( app_id=app_id, # 自己支付宝沙箱 APP ID notify_url="http://{}:8000/alipay/return/".format(server_ip), app_private_key_path=app_private_key_path, # 可以使用相对路径那个 alipay_public_key_path=alipay_public_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, debug=alipay_debug, # 默认False, return_url="http://{}:8000/alipay/return/".format(server_ip) ) verify_result = alipay.verify(processed_dict, sign) # 验证签名,如果成功返回True if verify_result: # POST中已经修改数据库订单状态,无需再GET中修改,且,GET中也得不到支付状态值 # 给支付宝返回一个消息,证明已收到异步通知 return Response('success') def post(self, request): # 。。。 ``` 我们也可以只传递`notify_url`,不传递`return_url`,但是有些情况下,我们希望支付成功后,可以返回到商户页面,比如个人订单中心,这就需要传递`return_url`。 当前在`return_url`的GET逻辑中,可以不用再次修改订单状态,因为在用户支付成功后,支付宝发送的POST请求已经修改了订单状态了。

很赞哦! (0)

文章交流

  • emoji
1人参与,1条评论
dj安狗 2019年10月31日 23:57
看不懂

当前用户

未登录,点击   登录

站点信息

  • 建站时间:网站已运行2073天
  • 系统信息:Linux
  • 后台程序:Python: 3.8.10
  • 网站框架:Django: 3.2.6
  • 文章统计:256 篇
  • 文章评论:60 条
  • 腾讯分析网站概况-腾讯分析
  • 百度统计网站概况-百度统计
  • 公众号:微信扫描二维码,关注我们
  • QQ群:QQ加群,下载网站的学习源码
返回
顶部
标题 换行 登录
网站