Django 对接 支付宝支付, 回调

时间:2023-05-19 17:27:34

平台

点击这里进入 蚂蚁金服开放平台

沙箱

点击这里进入 沙箱环境

初始界面

Django 对接 支付宝支付, 回调

设置公钥

下载创建秘钥工具

1.  进入文档中心 这里

2. 选中 电脑网站支付

Django 对接 支付宝支付, 回调

3. 进入后选中 API 列表 中的 统一收单下单并支付页面接口

Django 对接 支付宝支付, 回调

4. 进入后点击 sign 的 签名

Django 对接 支付宝支付, 回调

5. 进入后选择第一步, 然后选择 windows 下载工具

Django 对接 支付宝支付, 回调

生成秘钥

下载解压后运行

Django 对接 支付宝支付, 回调

Django 对接 支付宝支付, 回调

Django 对接 支付宝支付, 回调

会自动保存两份 txt, 这两个文件一定要妥善保管

Django 对接 支付宝支付, 回调

保存秘钥

将两个文件要做个修改, 最前和最后加上这两行, 且不要用中文命名

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

将两个文件保存在在项目文件的订单程序下

Django 对接 支付宝支付, 回调

同时再吧页面上的支付宝公钥复制下来保存在自己的项目中, 和上面类似的格式

Django 对接 支付宝支付, 回调

Django 对接 支付宝支付, 回调

设置公钥

注意是公钥,

Django 对接 支付宝支付, 回调

订单URL 生成

目录结构

Django 对接 支付宝支付, 回调

订单URL 生成逻辑文件

详细的API对接处理 这里  (内含所有的详细的参数选择)

相关的 python 实现代码 github 上都有,详情 这里

Python 官方没有提供指引, 可以使用此文件代码来实现 url 的生成

# _*_ coding:utf-8 _*_
__author__ = "yangtuo"
__date__ = "2019/4/24 16:25" # -*- coding: utf-8 -*- # pip install pycryptodome 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, appid, app_notify_url, app_private_key_path,
alipay_public_key_path, return_url, debug=False):
self.appid = appid
self.app_notify_url = app_notify_url
self.app_private_key_path = app_private_key_path
self.app_private_key = None
self.return_url = return_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, self.return_url)
return self.sign_data(data) def build_body(self, method, biz_content, return_url=None):
data = {
"app_id": self.appid,
"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 not None:
data["notify_url"] = self.app_notify_url
data["return_url"] = self.return_url return data def sign_data(self, data):
data.pop("sign", None)
# 排序后的字符串
unsigned_items = self.ordered_data(data)
unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
sign = self.sign(unsigned_string.encode("utf-8"))
# ordered_items = self.ordered_data(data)
quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 获得最终的订单信息字符串
signed_string = quoted_string + "&sign=" + quote_plus(sign)
return signed_string def ordered_data(self, data):
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):
# 开始计算签名
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 _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) if __name__ == "__main__":
return_url = 'http://127.0.0.1:8000/?total_amount=100.00&timestamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2FvhdDYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdpVQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&method=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0'
o = urlparse(return_url)
query = parse_qs(o.query)
processed_query = {}
ali_sign = query.pop("sign")[0] alipay = AliPay(
appid="",
app_notify_url="http://127.0.0.1:8000/alipay/return/",
app_private_key_path="../trade/keys/private_2048.txt",
alipay_public_key_path="../trade/keys/alipay_key_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
debug=True, # 默认False,
return_url="http://127.0.0.1:8000/alipay/return/"
) for key, value in query.items():
processed_query[key] = value[0]
print(alipay.verify(processed_query, ali_sign)) url = alipay.direct_pay(
subject="测试订单2",
out_trade_no="20170202sss",
total_amount=100,
return_url="http://127.0.0.1:8000/alipay/return/"
)
re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) print(re_url)

alipay.py

https://openapi.alipaydev.com/gateway.do?app_id=2016100100636029&biz_content=%7B%22subject%22%3A%22%5Cu6d4b%5Cu8bd5%5Cu8ba2%5Cu53552%22%2C%22out_trade_no%22%3A%2220170202sss%22%2C%22total_amount%22%3A100%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay&notify_url=http%3A%2F%2F127.0.0.1%3A8000%2Falipay%2Freturn%2F&return_url=http%3A%2F%2F127.0.0.1%3A8000%2Falipay%2Freturn%2F&sign_type=RSA2&timestamp=2019-04-24+16%3A40%3A17&version=1.0&sign=w4mdZOool9ne634Hf59g%2FV9cjj7ctzp37D5fs0U9xvOrDOBih%2FkpwrMXyWj5oVk%2FGETPx0RT7AkyOmnNSdF%2BTOCHkF4BAhABhbKLvaQfUqRdwIqGgvSKt18GGHVweg3BMh0hkrfzEuREVNkZlH2BpYD0UP5FS2PTVd90YnBJaWcJ%2BGyhizF5qlOAYIYFghbx9EKKo5e37peq%2BiVzme6Yui0FW%2ByGbtb54V2Slbo0iaPFq67pVUeBk%2BGprov6hBNxaVsz8O069z7IcIlzwFaSkYVxPB5dSAa6UWhMwCOdqMcO90LWCAhYYeF5yS%2BzJa1Mp95gQhHNTP2L5WfYp4T0uQ%3D%3D

生成的 url

订单付款

订单付款界面

基于alipay.py 中的参数进行相关的设置之后生成的 url 访问即可进入付款界面

Django 对接 支付宝支付, 回调

付款账号

沙箱环境的付款只能基于特定的账号或者指定的 沙箱支付宝软件才可以进行付款操作, 利用此账号付款后会基于是否设置回调决定下一步操作

Django 对接 支付宝支付, 回调

付款成功回调

回调基于 return_url 以及 notify_url 两字段都是非必填, 如果不设置支付成功后悔进入成功页面无任何跳转即回调

return_url  同步接口

Django 对接 支付宝支付, 回调

此跳转只在 PC 上支付成功后实现跳转, 只支持当前一次的请求处理

用户支付完成后, 支付宝基于此参数, 通过 GET 请求形式将部分支付结果参数通知到商户系统

notify_url  异步接口

Django 对接 支付宝支付, 回调

允许订单的异步请求, 实现订单的延迟或者另外平台的后续付款请求

用户支付完成后, 支付宝基于此参数, 通过 POST 请求形式将部分支付结果参数通知到商户系统

简述总结

notify_url 是支付宝模拟post数据给你,只要状态改变就会post给你

return_url 是跳转,用户付款后跳转到你的页面.只有第一次会通知你,以后不会了.

订单状态

订单状态是支付宝指定的4种, 在自己的代码中的订单model 的订单状态字段也最好遵循

    ORDER_STATUS = (
("TRADE_SUCCESS", "成功"),
("TRADE_CLOSED", "超时关闭"),
("WAIT_BUYER_PAY", "交易创建"),
("TRADE_FINISHED", "交易结束"),
("paying", "待支付"),
) pay_status = models.CharField(choices=ORDER_STATUS, default="paying", max_length=30, verbose_name="订单状态")

视图编写

前面又说两个 URL 的请求方式不同, 因此我们基于此进行区分, 然后通过在request 中取到相关的字段完成数据库的更新以及相关的跳转

return_url 最常见的请求是返回商户, 或者返回首页

notify_url 需要回传一个 "success" 信息给支付宝, 不然支付宝会一直像我们发送信息提示 ( 24h22m,8次 )

Django 对接 支付宝支付, 回调

具体代码

# 支付宝支付
class AlipayView(APIView): # 没有 model 就直接 APIView 就可以了 # 处理支付宝的return_url返回
def get(self, request):
processed_dict = {}
for key, value in request.GET.items():
processed_dict[key] = value sign = processed_dict.pop("sign", None) alipay = AliPay(
appid="20xxxxxxx029",
app_notify_url="http://127.0.0.1:8000/alipay/return/",
app_private_key_path=private_key_path,
alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
debug=True, # 默认False,
return_url="http://127.0.0.1:8000/alipay/return/"
) verify_re = alipay.verify(processed_dict, sign) if verify_re is True:
order_sn = processed_dict.get('out_trade_no', None)
trade_no = processed_dict.get('trade_no', None)
trade_status = processed_dict.get('trade_status', None) existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
for existed_order in existed_orders:
existed_order.pay_status = trade_status
existed_order.trade_no = trade_no
existed_order.pay_time = datetime.now()
existed_order.save() response = redirect("index")
response.set_cookie("nextPath", "pay", max_age=3)
return response
else:
response = redirect("index")
return response # 处理支付宝的notify_url
def post(self, request):
processed_dict = {}
for key, value in request.POST.items():
processed_dict[key] = value sign = processed_dict.pop("sign", None) alipay = AliPay(
appid="20xxxxxxx029",
app_notify_url="http://127.0.0.1:8000/alipay/return/",
app_private_key_path=private_key_path,
alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
debug=True, # 默认False,
return_url="http://127.0.0.1:8000/alipay/return/"
) verify_re = alipay.verify(processed_dict, sign) if verify_re is True:
order_sn = processed_dict.get('out_trade_no', None) # 商户网站唯一订单号
trade_no = processed_dict.get('trade_no', None) # 支付宝交易流水号
trade_status = processed_dict.get('trade_status', None) # 支付宝订单状态
# 查询本地是否有此订单
existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
# 更新订单状态
for existed_order in existed_orders:
order_goods = existed_order.goods.all()
for order_good in order_goods:
goods = order_good.goods
goods.sold_num += order_good.goods_num
goods.save() existed_order.pay_status = trade_status
existed_order.trade_no = trade_no
existed_order.pay_time = datetime.now()
existed_order.save() return Response("success")

其他联调

订单的序列化组件中的 alipay_url 字段生成

    # 支付宝相关的字段
alipay_url = serializers.SerializerMethodField(read_only=True) def get_alipay_url(self, obj):
alipay = AliPay(
appid="",
app_notify_url="http://127.0.0.1:8000/alipay/return/",
app_private_key_path=private_key_path,
alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
debug=True, # 默认False,
return_url="http://127.0.0.1:8000/alipay/return/"
) url = alipay.direct_pay(
subject=obj.order_sn,
out_trade_no=obj.order_sn,
total_amount=obj.order_mount,
)
re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) return re_url

事后补充

支付宝改版后新版的支付宝的 return_url  返回数据中不再包含 trade_status, 即无法通过此参数进行订单状态的修改了.

但是 notify_url 不受影响, 对此的解释很大可能是安全问题,  需要商户自行再次进行一个判断状态想的逻辑处理.

Django 对接 支付宝支付, 回调

修正后源码

class AlipayView(APIView):
def get(self, request):
"""
处理支付宝的return_url返回
"""
processed_dict = {}
# 1. 获取GET中参数
for key, value in request.GET.items():
processed_dict[key] = value
# 2. 取出sign
sign = processed_dict.pop("sign", None) # 3. 生成ALipay对象
alipay = AliPay(
appid="",
app_notify_url="http://115.159.122.64:8000/alipay/return/",
app_private_key_path=private_key_path,
alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
debug=True, # 默认False,
return_url="http://115.159.122.64:8000/alipay/return/"
) verify_re = alipay.verify(processed_dict, sign) # 这里可以不做操作。因为不管发不发return url。notify url都会修改订单状态。
if verify_re is True:
order_sn = processed_dict.get('out_trade_no', None)
trade_no = processed_dict.get('trade_no', None) existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
for existed_order in existed_orders:
existed_order.trade_no = trade_no
existed_order.pay_time = datetime.now()
existed_order.save() response = redirect("/index/#/app/home/member/order")
# response.set_cookie("nextPath","pay", max_age=3)
return response
else:
response = redirect("index")
return response def post(self, request):
"""
处理支付宝的notify_url
"""
# 1. 先将sign剔除掉
processed_dict = {}
for key, value in request.POST.items():
processed_dict[key] = value sign = processed_dict.pop("sign", None) # 2. 生成一个Alipay对象
alipay = AliPay(
appid="",
app_notify_url="http://115.159.122.64:8000/alipay/return/",
app_private_key_path=private_key_path,
alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
debug=True, # 默认False,
return_url="http://115.159.122.64:8000/alipay/return/"
) # 3. 进行验签,确保这是支付宝给我们的
verify_re = alipay.verify(processed_dict, sign) # 如果验签成功
if verify_re is True:
order_sn = processed_dict.get('out_trade_no', None)
trade_no = processed_dict.get('trade_no', None)
trade_status = processed_dict.get('trade_status', None) # 查询数据库中存在的订单
existed_orders = OrderInfo.objects.filter(order_sn=order_sn)
for existed_order in existed_orders:
# 订单商品项
order_goods = existed_order.goods.all()
# 商品销量增加订单中数值
for order_good in order_goods:
goods = order_good.goods
goods.sold_num += order_good.goods_num
goods.save() # 更新订单状态,填充支付宝给的交易凭证号。
existed_order.pay_status = trade_status
existed_order.trade_no = trade_no
existed_order.pay_time = datetime.now()
existed_order.save()
# 将success返回给支付宝,支付宝就不会一直不停的继续发消息了。
return Response("success")