沙箱环境

- 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})