沙箱环境
- APPID: 2021000122610340
- 应用名称: sandbox应用:2088621995186304
- 绑定的商家账号(PID):2088621995186304
- 支付宝网关地址:https://openapi.alipaydev.com/gateway.do
- 支付宝SDK(官方没有提供Python,大神写好了)
根据github文档操作即可
pip install python-alipay-sdk
- github文档地址
- https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
- 美多商城文档: http://47.101.37.192/%E8%AF%BE%E4%BB%B6/%E7%BE%8E%E5%A4%9A%E5%95%86%E5%9F%8E%E8%AF%BE%E4%BB%B6/payment/alipay-introduce.html
-
在Linux/Mac系统下,使用以下命令生成项目的'公私匙'
- 先生成私匙,让依据私匙,生成对应的公匙
$ openssl
$ OpenSSL> genrsa -out app_private_key.pem 2048 # 制作私钥RSA2
$ OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥
$ OpenSSL> exit
-
把生成的'公私匙'放到项目app底下
-
配置美多商城公钥
- 将payment.keys.app_public_key.pem文件中内容上传到支付宝
-
配置支付宝公钥
- 将支付宝公钥内容拷贝到payment.keys.alipay_public_key.pem文件中
-
公私匙小结(非对称加密)
-
项目和支付宝各自持有自己的私匙(保密)
-
项目和支付宝各自持有对方的公匙(公开)
-
-
所谓 非对称加密
- 相当于出门锁门用的是A钥匙,进门开门需要拿B钥匙才能打开
创建支付宝数据库模型
### payment.models
from django.db import models
from utils.models import BaseModel
from orders.models import OrderInfo
class Payment(BaseModel):
"""支付信息
- 订单编号
- 支付宝支付编号
"""
order = models.ForeignKey(OrderInfo,on_delete=models.CASCADE,verbose_name='订单')
trade_id = models.CharField(max_length=100,unique=True,null=True,blank=True,verbose_name="支付编号")
class Meta:
db_table = 'tb_payment'
verbose_name = '支付信息'
verbose_name_plural = verbose_name
- 固定配置
### settings
......
#---------支付宝-----------#
ALIPAY_APPID = '2016091000551154'
ALIPAY_DEBUG = True
ALIPAY_URL = 'https://openapi.alipaydev.com/gateway.do'
- views逻辑
import os
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from django.conf import settings
from alipay import AliPay, DCAliPay, ISVAliPay
from alipay.utils import AliPayConfig
from orders.models import OrderInfo
class PaymentView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request,order_id):
user = request.user
# 校验订单的有效性
try:
order_obj = OrderInfo.objects.get(order_id=order_id,user=user,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'])
except OrderInfo.DoesNotExist:
return Response({'message':'订单有误'},status=status.HTTP_400_BAD_REQUEST)
local_file_path = os.path.abspath(__file__)
alipay = AliPay(
appid=settings.ALIPAY_APPID,
app_notify_url=None, # 默认回调 url
app_private_key_string=os.path.join(local_file_path,'keys/app_private_key.pem'),
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_string=os.path.join(local_file_path,'keys/app_public_key.pem'),
sign_type="RSA2", # RSA 或者 RSA2
debug=settings.ALIPAY_DEBUG, # 默认 False
)
# 调用SDK方法得到支付链接后面的查询参数
# 电脑网址支付,需跳转到 https://openapi.alipay.com/gateway.do? + order_string
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order_id, # 订单编号
total_amount=str(order_obj.total_amount), # 不认识Decimal,这里要转换类型
subject='美多商城{}'.format(order_id), # 标题
return_url="http://www.meiduo.site:8080/pay_success.html", # 支付成功以后回调地址
# notify_url="https://example.com/notify" # 可选,不填则使用默认 notify url
)
# 拼接好支付链接
alipay_url = settings.ALIPAY_URL + '?' + order_string
return Response({'alipay_url':alipay_url})
支付宝对接流程
- 当用户在项目界面,选择支付宝支付,点击支付的时候,我们要把url跳向支付宝(携带参数,例如回调地址)
- 用户在支付宝完成支付后,支付宝向我们提供的回调地址发请求(携带支付后的多个参数)
- 在支付宝返回的多个参数中,sign是加密后,我们利用支付宝的公匙解密
然后和项目的原始数据作对比,如果一致,说明数据没有被篡改,继续支付后的逻辑
若数据不一致,说明被篡改过,直接抛异常
- 当支付成功调回项目页面的时候,前端立即把支付宝提供的一堆的查询字符串发给后端
# pay_success.html
......
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
host,
trade_id: '',
},
mounted: function(){
// 页面加载完毕,立即发送查询字符串到后端
axios.put(this.host+'/payment/status/'+document.location.search,{}, {
responseType: 'json'
})
.then(response => {
this.trade_id = response.data.trade_id;
})
.catch(error => {
console.log(error.response);
})
}
})
</script>
- 后端主要逻辑分析
- 获取前端的查询字符串参数,转换成dict
- 把sign从dict从移除(后续要校验)
- 创建支付宝对象,调用 verify()验证 支付结果是否为支付宝回传回来的
- 取出 美多商城订单编号&支付宝交易号,绑到一起,存储到mysql
- 修改支付成功以后的订单状态,把支付宝交易响应前端
### payment.urls
from django.conf.urls import url
from . import views
urlpatterns = [
# 获取支付宝支付url
url(r"^orders/(?P<order_id>\d+)/payment/$",views.PaymentView.as_view()),
# 验证支付是否合法
url(r"^payment/status/$",views.PaymentStatusView.as_view()),
]
### payment.views
class PaymentStatusView(APIView):
def put(self,request):
query_dict = request.query_params
dict_data = query_dict.dict()
sign = dict_data.pop('sign')
alipay = AliPay(
appid=settings.ALIPAY_APPID,
app_notify_url=None,
app_private_key_string=os.path.join(local_file_path, 'keys/app_private_key.pem'),
alipay_public_key_string=os.path.join(local_file_path, 'keys/app_public_key.pem'),
sign_type="RSA2",
debug=settings.ALIPAY_DEBUG,
)
success = alipay.verify(dict_data,sign)
if not success:
return Response({'message':'非法支付'},status=status.HTTP_403_FORBIDDEN)
order_id = dict_data.get('out_trade_no')
trade_no = dict_data.get('trade_no')
Payment.objects.create(
order_id=order_id,
trade_id=trade_no
)
filter_queryset = OrderInfo.objects.filter(order_id=order_id,status=OrderInfo.ORDER_STATUS_ENUM['UNPAID'])
filter_queryset.update(status=OrderInfo.ORDER_STATUS_ENUM['UNSEND'])
return Response({'trade_id':trade_no})