专题:Vue+Django REST framework前后端分离生鲜电商
Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。
Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ;
Django版本:2.2、djangorestframework:3.9.2。
前端Vue模板可以直接联系我拿。
更多内容请点击 我的博客 查看,欢迎来访。
访问 https://github.com/liyaopinner/mxshop_sources 下载 “慕学生鲜电商的部分资源文件- alipay.py ”
复制内容放在 utils/alipay.py 文件中,对其进行修改,改为自己项目所需的
# -*- 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
在项目下创建 ProjectConfig.ini 配置文件,该配置文件主要是用来记录项目中涉密的配置,比如连接数据库帐密,服务器信息等。结构如下
[DjangoOnlineFreshSupermarket]
server_ip=xx.ip.ip.xx
接下来在 utils/alipay.py 中添加一个函数,用于获取这个服务器IP
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函数,测试下能够正确获取
if __name__ == "__main__":
print(get_server_ip())
server_ip = get_server_ip() # 得到自己服务器的IP地址,也就是之前同步项目上去的
在 main 函数中添加订单支付测试
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)
直接运行即可
这时候会得到一个链接
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¬ify_url=http%3A%2F%2Fxx.ip.ip.xx%3A8000%2F&return_url=http%3A%2F%2Fxx.ip.ip.xx%3A8000%2F&sign_type=RSA2×tamp=2019-08-07+16%3A09%3A25&version=1.0&sign=crNYPmSRAccnEb%2BnvnYqgG6qpp4n5NrOHP4sBLyjNBWws6RWS5JrGntGX%2FG2SGqf21dIwvUtt5sV5XY%2Bol1dId%2Bn%2BVBykzJShjB4Y0mt%2Bgm498Tv5ecUCUFvOFXY%2BpWRu3HiuuiJXxCHHzEZ795sw1x8xSQaKZCTEHCBZsfwexKwE1UKsCWLv1cfgjO3O8rCMziSASTMta%2BlfmPcZTdO9tTI9qTXE%2Bq2TMQpZWqBZvN1LPKHdzv1TZL3efjI64qEKglYK6KUCtUgNJoUBJrmYj4Ao3XZMro06Lu73MTPpheg8v56yBXGe4FyMdpxOvOS2t%2FnRZtyM2cx5io6lUoScQ%3D%3D
浏览器访问该链接可以看到沙箱支付页面
可以使用登录账户付款(在开放平台-沙箱环境-沙箱账号中),也可以下载支付宝沙箱测试应用进行扫码支付。
支付完成后就会停留在该界面,如果想要跳回商户界面,需要配置return_url
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)
现在修改订单号测试,如果不修改,支付宝会提示该订单已支付。
支付完成会自动跳转到return_url="http://{}:8000/".format(server_ip)
配置的地址,并且传入很多参数
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×tamp=2019-08-07+16%3A31%3A37
当用户创建好订单之后,跳转到支付宝页面进行支付,如果用户通过扫码或登录支付宝,就会创建一个支付宝订单,确认支付后,支付宝会自动跳转到return_url
配置的商户页面,我们可以获取到url中的参数,来验证用户是否已经支付,如果支付就修改订单状态。
还有一种情况就是创建订单,通过扫码,或登录支付宝创建支付宝订单后,用户并没有确认支付,而是关闭了该页面,在手机上个人订单中去支付,那么return_url
就无效了,而此时,我们的应用就无法判断该订单是否已支付。这时候notify_url
就有用了,支付宝会通过异步方式,向该url发起一个请求(POST),并传递一些参数,我们的应用获取参数,解析其中的信息,对订单状态进行修改。
用户支付后,会跳回return_url
,我们需要对url进行验证,判定用户是否确认时已支付的,因为数据在传输过程中可能会被截获修改,如果不去做验证,系统上修改订单状态后,却没收到款。
获取url中所有参数,通过支付宝公钥解密
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×tamp=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视图,用于处理支付宝的返回
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
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()
。
编写该接口,用户处理支付宝支付后,异步通知接口。首先来确认下,支付宝完成支付后,是否有请求访问到该接口。
先修改 utils/alipay.py 将return_url
和notify_url
都配置成上面刚创建的url,然后修改下新的订单号
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,点击项目选择后上传。
将运行配置设置为服务器的环境后
重新Debug启动,将post
方法标记为断点
直接运行 utils/alipay.py ,会输出一个链接,在浏览器中访问该链接并进行支付,支付完成后,后台进入Debug,查看request
的内容
拿到这个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) | 是 | 请参考异步返回结果的验签 | 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) | 否 | 交易目前所处的状态,见交易状态说明 | TRADE_CLOSED |
业务参数中 out_trade_no 可以获取商户订单号,通过该订单号,可以将数据库该订单状态进行修改。
支付宝交易状态说明
枚举名称 | 枚举说明 |
---|---|
WAIT_BUYER_PAY | 交易创建,等待买家付款 |
TRADE_CLOSED | 未付款交易超时关闭,或支付完成后全额退款 |
TRADE_SUCCESS | 交易支付成功 |
TRADE_FINISHED | 交易结束,不可退款 |
可以将这些状态定义到订单信息model中,修改 apps/trade/models.py 中的OrderInfo
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='订单状态')
# 字段省略
在 src/views/member/order.vue 订单列表页面,进行以下修改
待支付
已支付
修改为
待支付
已支付
在 src/views/member/orderDetail.vue 订单详情页面也做同样的修改
待支付
已支付
修改为
待支付
已支付
可以将支付宝相关的配置保存在 DjangoOnlineFreshSupermarket/settings.py 文件中,其他文件直接调用即可,无需重复写多次,如果后面配置有变化,直接修改一个地方即可,比如从沙箱转移到正式环境后,只需要将alipay_debug = False
即可
# 支付宝相关配置
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")
修改 apps/trade/views.py 中的AliPayView
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')
,否则支付宝会重复发通知。
对比下POST和GET中的内容
[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'}
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请求已经修改了订单状态了。