支付宝支付&内网穿透

  • 一 沙箱环境
  • 二 python第三方模块python-alipay-sdk
  • 三 python-alipay-sdk二次封装
  • 四 支付接口
  • 五 内网穿透
  • 5.1 cpolar软件
  • 5.2 测试支付宝post回调
  • 一 沙箱环境

    注册认证沙箱环境:https://openhome.alipay.com/platform/appDaily.htm?tab=info

    下载支付宝开放平台密钥工具:https://opendocs.alipay.com/common/02kipk


    在开发中心的沙箱应用下设置应用公钥:填入生成的公钥文件中的内容。

    二 python第三方模块python-alipay-sdk

    https://github.com/fzlee/alipay

    中文文档:https://github.com/fzlee/alipay/blob/master/README.zh-hans.md

    # 安装 python-alipay-sdk
    pip install python-alipay-sdk --upgrade
    

    新建alipay_public_key.pem文件和app_private_key.pem文件

    alipay_public_key.pem

    -----BEGIN PUBLIC KEY-----
    支付宝公钥
    -----END PUBLIC KEY-----
    

    app_private_key

    -----BEGIN RSA PRIVATE KEY-----
    私钥
    -----END RSA PRIVATE KEY-----
    

    支付宝链接:

    开发:https://openapi.alipay.com/gateway.do
    沙箱:https://openapi.alipaydev.com/gateway.do
    

    支付流程:

    三 python-alipay-sdk二次封装

    结构

    libs
        ├── iPay  							# aliapy二次封装包
        │   ├── __init__.py 				# 包文件
        │   ├── pem							# 公钥私钥文件夹
        │   │   ├── alipay_public_key.pem	# 支付宝公钥文件
        │   │   ├── app_private_key.pem		# 应用私钥文件
        │   ├── pay.py						# 支付文件
        └── └── settings.py  	            # 配置文件
    

    pem/alipay_public_key.pem

    -----BEGIN PUBLIC KEY-----
    拿应用公钥跟支付宝换来的支付宝公钥
    -----END PUBLIC KEY-----
    

    pem/app_private_key.pem

    -----BEGIN RSA PRIVATE KEY-----
    通过支付宝公钥私钥签发软件签发的应用私钥
    -----END RSA PRIVATE KEY-----
    

    settings.py

    import os
    
    # 应用ID
    APPID = '2021000121697151'
    
    
    # 应用私钥
    APP_PRIVATE_KEY_STRING = open(
        os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'app_private_key.pem')).read()
    # 支付宝公钥
    ALIPAY_PUBLIC_KEY_STRING = open(
        os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'alipay_public_key.pem')).read()
    
    SIGN = 'RSA2'
    
    # 是否是支付宝测试环境(沙箱环境),如果采用真是支付宝环境,配置False
    DEBUG = True
    
    # 支付网关
    GATEWAY = 'https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'
    

    pay.py

    from . import settings
    
    # 初始化得到对象,传入一堆参数
    alipay = AliPay(
        appid=settings.APPID,  # app的id号
        app_notify_url=None,  # 默认回调 url
        app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
        # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
        sign_type=settings.SIGN,  # RSA 或者 RSA2
        debug=settings.DEBUG,  # 默认 False
        verbose=False,  # 输出调试数据
        config=AliPayConfig(timeout=15)  # 可选,请求超时时间
    )
    

    init

    from .pay import alipay
    from .settings import GATEWAY
    

    在项目的配置文件中配置支付宝回调接口

    # 上线后必须换成公网地址
    # 后台基URL
    HOST_URL = 'http://127.0.0.1:8000'
    # 前台基URL
    LUFFY_URL = 'http://127.0.0.1:8080'
    # 支付宝同步异步回调接口配置
    # 后台异步回调接口 :支付宝post回调地址:后端地址,后端配合一个接口
    
    # 开发环境:这里的地址是内网穿透地址,内网穿透后面有解释。
    NOTIFY_URL = "https://287b1b58.r3.cpolar.top/api/v1/order/success/"
    
    # 前台同步回调接口,没有 / 结尾 支付宝get回调地址:前端地址,前端配合一个支付成功的页面组件
    RETURN_URL = LUFFY_URL + "/pay/success"
    

    四 支付接口

    1)支付接口(需要登录认证:是谁):post方法,前台提交商品等信息,得到支付链接。

    分析:支付宝回调
    同步:get给前台 => 前台可以在收到支付宝同步get回调时,ajax异步在给消息同步给后台,也采用get,后台处理前台的get请求。
    异步:post给后台 => 后台直接处理支付宝的post请求。

    2)支付回调接口(不需要登录认证:哪个订单(订单信息中有非对称加密)、支付宝压根不可能有你的token):
    get方法:处理前台来的同步回调(不一定能收得到,所有不能在该方法完成后台订单状态等信息操作)。
    post方法:处理支付宝来的异步回调。

    3)订单状态确认接口:随你前台任何时候来校验订单状态的接口。

    models.py

    from django.db import models
    from user.models import User
    from course.models import Course
    
    
    # Create your models here.
    # 订单表
    class Order(models.Model):
        """订单模型"""
        status_choices = (
            (0, '未支付'),
            (1, '已支付'),
            (2, '已取消'),
            (3, '超时取消'),
        )
        pay_choices = (
            (1, '支付宝'),
            (2, '微信支付'),
        )
        subject = models.CharField(max_length=150, verbose_name="订单标题")
        total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0)
        # 唯一的,咱们生成的, uuid
        out_trade_no = models.CharField(max_length=64, verbose_name="订单号", unique=True)
        # 支付宝 成功后,会有个流水号
        trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号")
        # 订单状态
        order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
        pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
        # 支付时间:支付宝支付完成返回的数据中有这个字段
        pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
        # 订单创建时间,不一定支付
        created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
        user = models.ForeignKey(User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False,
                                 verbose_name="下单用户")
    
        class Meta:
            db_table = "luffy_order"
            verbose_name = "订单记录"
            verbose_name_plural = "订单记录"
    
        def __str__(self):
            return "%s - ¥%s" % (self.subject, self.total_amount)
    
        @property
        def courses(self):
            data_list = []
            for item in self.order_courses.all():
                data_list.append({
                    "id": item.id,
                    "course_name": item.course.name,
                    "real_price": item.real_price,
                })
            return data_list
    
    
    # 订单详情表
    class OrderDetail(models.Model):
        """订单详情"""
        order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False,
                                  verbose_name="订单")
        course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False,
                                   verbose_name="课程")
        price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价")
        real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价")
    
        class Meta:
            db_table = "luffy_order_detail"
            verbose_name = "订单详情"
            verbose_name_plural = "订单详情"
    
        def __str__(self):
            try:
                return "%s的订单:%s" % (self.course.name, self.order.out_trade_no)
            except:
                return super().__str__()
    

    views.py

    
    # 订单接口
    class OrderView(GenericViewSet):
        queryset = Order.objects.all()
        serializer_class = OrderSerializer
        authentication_classes = [JSONWebTokenAuthentication]
        permission_classes = [IsAuthenticated]
    
        def create(self, request, *args, **kwargs):
            serializer = self.get_serializer(data=request.data, context={'request': request})
            serializer.is_valid(raise_exception=True)
            serializer.save()
            pay_url = serializer.context.get('pay_url')
            return APIResponse(pay_url=pay_url)
    
    
    # # 回调接口
    class PaySuccessView(ViewSet):
        def list(self, request):
            try:
                out_trade_no = request.query_params.get('out_trade_no')
                Order.objects.get(out_trade_no=out_trade_no, order_status=1)
                return APIResponse()
            except Exception as e:
                raise APIException('该订单还没支付成功')
    
        def create(self, request):
            try:
                # from django.http.request import QueryDict
                # print(type(request.data))
                result_data = request.data.dict()  # 回调回来编码格式是urlencoded,QueryDic对象---》.dict--->转成真正的字典对象
                print(result_data)
                out_trade_no = result_data.get('out_trade_no')
                signature = result_data.pop('sign')  # 如果是QueryDic对象不允许pop
                from libs import alipay_common
                # 验证签名,result_data和signature验证签名,sdk帮咱们写好了,一定要验证签名
                result = alipay_common.alipay.verify(result_data, signature)
                if result and result_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
                    # 完成订单修改:订单状态、流水号、支付时间
                    # pay_time=result_data.get('gmt_payment') 真正获取支付时间
                    Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1,
                                                                           pay_time=result_data.get('gmt_payment'),
                                                                           trade_no=result_data.get('trade_no'))
                    # 完成日志记录
                    logger.warning('%s订单支付成功' % out_trade_no)
                    return Response('success')  # 支付宝要求的格式
                else:
                    logger.error('%s订单支付失败' % out_trade_no)
                    return Response('failed')
            except:
                return Response('failed')
    

    serializer.py

    from rest_framework import serializers
    from .models import Order, OrderDetail
    from course.models import Course
    from rest_framework.exceptions import APIException
    from uuid import uuid4
    from libs.alipay_common import alipay, GATEWAY
    from django.conf import settings
    
    class OrderSerializer(serializers.ModelSerializer):
        courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
    
        class Meta:
            model = Order
            fields = ['courses', 'total_amount', 'subject', 'pay_type']
    
        def _check_amount(self, attrs):
            total_amount = attrs.get('total_amount')
            real_total = 0
            for course_obj in attrs.get('courses'):
                real_total += course_obj.price
            print(real_total)
            if total_amount != real_total:
                raise APIException('金额不一致')
            return float(real_total)
    
        def _get_out_trade_no(self):
            return str(uuid4())
    
        def _get_user(self):
            return self.context.get('request').user
    
        def _get_pay_url(self, out_trade_no, total_amount, subject):
            order_string = alipay.api_alipay_trade_page_pay(
                out_trade_no=out_trade_no,
                total_amount=total_amount,
                subject=subject,
                return_url=settings.RETURN_URL,  # get回调地址
                notify_url=settings.NOTIFY_URL  # post回调地址
            )
            return GATEWAY + order_string
    
        def _before_create(self, attrs, user, pay_url, out_trade_no):
            self.context['pay_url'] = pay_url
            attrs['user'] = user
            attrs['out_trade_no'] = out_trade_no
    
        def validate(self, attrs):
            # 1 校验价格:计算一下总价格和后端算出来的总价格是否一致
            total_amount = self._check_amount(attrs)
            # 2)生成订单号:唯一的
            out_trade_no = self._get_out_trade_no()
            # 3)支付用户:request.user
            user = self._get_user()
            # 4)支付链接生成,放入到self.context中
            pay_url = self._get_pay_url(out_trade_no, total_amount, attrs.get('subject'))
            # 5)入库(两个表)的信息准备:重写create方法
            self._before_create(attrs, user, pay_url, out_trade_no)
            return attrs
    
        def create(self, validated_data):
            courses = validated_data.pop('courses')
            order_obj = Order.objects.create(**validated_data)
            # 存订单详情表
            for course_obj in courses:
                OrderDetail.objects.create(order=order_obj, course=course_obj, price=course_obj.price,
                                           real_price=course_obj.price)
            return order_obj
    

    五 内网穿透

    内网穿透也叫做内网映射,也叫“NAT穿透”。

    一句话来说就是,让外网能访问你的内网;把自己的内网(主机)当成服务器,让外网能访问。

    5.1 cpolar软件

    官网下载:https://www.cpolar.com/


    打开软件


    状态显示active为已激活

    5.2 测试支付宝post回调

    因为现在项目还没有上线,支付宝不能访问到我们的内网,所以做了内网穿透,将支付宝的post回调地址改成cpolar的公网地址,会自动映射到配置的本地8000端口上,这样就实现了外网间接访问内网。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 支付宝支付&内网穿透

    发表评论