zoukankan      html  css  js  c++  java
  • Django 支付宝付款接口调用方法及前台渲染

    支付宝支付

    # 1、在沙箱环境下实名认证:https://openhome.alipay.com/platform/appDaily.htm?tab=info
    
    # 2、电脑网站支付API:https://docs.open.alipay.com/270/105898/
    
    # 3、完成RSA密钥生成:https://docs.open.alipay.com/291/105971
    
    # 4、在开发中心的沙箱应用下设置应用公钥:填入生成的公钥文件中的内容
    
    # 5、Python支付宝开源框架:https://github.com/fzlee/alipay
    # >: pip install python-alipay-sdk --upgrade
    
    # 7、公钥私钥设置
    """
    # alipay_public_key.pem
    -----BEGIN PUBLIC KEY-----
    支付宝公钥
    -----END PUBLIC KEY-----
    
    # app_private_key.pem
    -----BEGIN RSA PRIVATE KEY-----
    用户私钥
    -----END RSA PRIVATE KEY-----
    """
    
    # 8、支付宝链接
    """
    开发:https://openapi.alipay.com/gateway.do
    沙箱:https://openapi.alipaydev.com/gateway.do
    """
    

    aliapy二次封装包

    依赖

    >: pip install python-alipay-sdk --upgrade
    

    结构

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

    setting.py

    import os
    # 支付宝应用id
    APP_ID = '2016093000631831'
    # 默认异步回调的地址,通常设置None就行
    APP_NOTIFY_URL = None
    # 应用私钥文件路径
    APP_PRIVATE_KEY_PATH = os.path.join(os.path.dirname(__file__), 'keys', 'app_private_key.pem')
    # 支付宝公钥文件路径
    ALIPAY_PUBLIC_KEY_PATH = os.path.join(os.path.dirname(__file__), 'keys', 'alipay_public_key.pem')
    # 签名方式
    SIGN_TYPE = 'RSA2'
    # 是否是测试环境
    DEBUG = True
    

    _init_.py

    from alipay import AliPay
    from .settings import *
    # 对外提供
    from .settings import RETURN_URL, NOTIFY_URL
    # 对外提供支付对象
    alipay = AliPay(
        appid=APP_ID,
        app_notify_url=APP_NOTIFY_URL,
        app_private_key_path=APP_PRIVATE_KEY_PATH,
        alipay_public_key_path=ALIPAY_PUBLIC_KEY_PATH,
        sign_type=SIGN_TYPE,
        debug=DEBUG
    )
    

    alipay_public_key.pem

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

    app_private_key.pem

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

    补充:dev.py

    # 上线后必须换成官网地址
    # 同步回调的接口(get),前后台分离时一般设置前台页面url
    RETURN_URL = 'http://127.0.0.1:8080/pay/success'
    # 异步回调的接口(post),一定设置为后台服务器接口
    NOTIFY_URL = 'http://127.0.0.1:8000/order/success/'
    

    支付模块

    order/models.py

    """
    订单:订单号、流水号、价格、用户
    订单详情(自定义关系表):订单、课程
    """
    
    from django.db import models
    from utils.model import BaseModel
    from user.models import User
    from course.models import Course
    
    
    class Order(BaseModel):
        """订单模型"""
        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)
        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="支付时间")
        user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING, db_constraint=False,
                                 verbose_name="下单用户")
    
        # 多余字段
        orders = models.IntegerField(verbose_name='显示顺序', default=0)
    
        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(BaseModel):
        """订单详情"""
        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):
            return "%s订单(%s)" % (self.course.name, self.order.order_number)
    
    

    支付接口生成支付链接

    import time
    from rest_framework.views import APIView
    from utils.response import APIResponse
    from libs.iPay import alipay
    from . import authentications, serializers
    from rest_framework.permissions import IsAuthenticated
    from django.conf import settings
    # 获取前台 商品名、价格,产生 订单、支付链接
    class PayAPIView(APIView):
        authentication_classes = [authentications.JWTAuthentication]
        permission_classes = [IsAuthenticated]
        def post(self, request, *args, **kwargs):
            # 前台提供:商品名、总价、支付方式
            request_data = request.data
            # 后台产生:订单号、用户
            out_trade_no = '%d' % time.time() * 2
            request_data['out_trade_no'] = out_trade_no
            request_data['user'] = request.user.id
    
            # 反序列化数据,用于订单生成前的校验
            order_ser = serializers.OrderModelSerializer(data=request_data)
            if order_ser.is_valid():
                # 生成订单,订单默认状态为:未支付
                order = order_ser.save()
                # 支付链接的参数
                order_string = alipay.api_alipay_trade_page_pay(
                    subject=order.subject,
                    out_trade_no=order.out_trade_no,
                    total_amount='%.2f' % order.total_amount,
                    return_url=settings.RETURN_URL,
                    notify_url=settings.NOTIFY_URL
                )
                # 形成支付链接:alipay._gateway根据字符环境DEBUG配置信息,决定是沙箱还是真实支付环境
                pay_url = '%s?%s' % (alipay._gateway, order_string)
                return APIResponse(0, 'ok', pay_url=pay_url)
    
    
            return APIResponse(1, 'no ok', results=order_ser.errors)
    

    前台回调接口的页面

    <template>
        <div class="pay-success">
            <Header/>
            <div class="main">
                <div class="title">
                    <div class="success-tips">
                        <p class="tips">您已成功购买 1 门课程!</p>
                    </div>
                </div>
                <div class="order-info">
                    <p class="info"><b>订单号:</b><span>{{ result.out_trade_no }}</span></p>
                    <p class="info"><b>交易号:</b><span>{{ result.trade_no }}</span></p>
                    <p class="info"><b>付款时间:</b><span><span>{{ result.timestamp }}</span></span></p>
                </div>
                <div class="study">
                    <span>立即学习</span>
                </div>
            </div>
            <Footer/>
        </div>
    </template>
    
    <script>
        import Header from "@/components/Header"
        import Footer from "@/components/Footer"
    
        export default {
            name: "Success",
            data() {
                return {
                    result: {},
                };
            },
            created() {
                // 判断登录状态
                let token = this.$cookies.get('token');
                if (!token) {
                    this.$message.error('非法请求');
                    this.$router.go(-1)
                }
    
    
                localStorage.this_nav = '/';
                if (!location.search.length) return;
                let params = location.search.substring(1);
                let items = params.length ? params.split('&') : [];
                //逐个将每一项添加到args对象中
                for (let i = 0; i < items.length; i++) {
                    let k_v = items[i].split('=');
                    //解码操作,因为查询字符串经过编码的
                    let k = decodeURIComponent(k_v[0]);
                    let v = decodeURIComponent(k_v[1]);
                    this.result[k] = v;
                    // this.result[k_v[0]] = k_v[1];
                }
                // console.log(this.result);
    
                // 把地址栏上面的支付结果,转发给后端
                this.$axios({
                    url: this.$settings.base_url + '/order/success/' + location.search,
                    method: 'patch',
                    headers: {
                        Authorization: token
                    }
                }).then(response => {
                    console.log(response.data);
                }).catch(() => {
                    console.log('支付结果同步失败');
                })
            },
            components: {
                Header,
                Footer,
            }
        }
    </script>
    
    <style scoped>
    
        .main {
            padding: 60px 0;
            margin: 0 auto;
             1200px;
            background: #fff;
        }
    
        .main .title {
            display: flex;
            -ms-flex-align: center;
            align-items: center;
            padding: 25px 40px;
            border-bottom: 1px solid #f2f2f2;
        }
    
        .main .title .success-tips {
            box-sizing: border-box;
        }
    
        .title img {
            vertical-align: middle;
             60px;
            height: 60px;
            margin-right: 40px;
        }
    
        .title .success-tips {
            box-sizing: border-box;
        }
    
        .title .tips {
            font-size: 26px;
            color: #000;
        }
    
    
        .info span {
            color: #ec6730;
        }
    
        .order-info {
            padding: 25px 48px;
            padding-bottom: 15px;
            border-bottom: 1px solid #f2f2f2;
        }
    
        .order-info p {
            display: -ms-flexbox;
            display: flex;
            margin-bottom: 10px;
            font-size: 16px;
        }
    
        .order-info p b {
            font-weight: 400;
            color: #9d9d9d;
            white-space: nowrap;
        }
    
        .study {
            padding: 25px 40px;
        }
    
        .study span {
            display: block;
             140px;
            height: 42px;
            text-align: center;
            line-height: 42px;
            cursor: pointer;
            background: #ffc210;
            border-radius: 6px;
            font-size: 16px;
            color: #fff;
        }
    </style>
    

    支付完成订单校验的接口

    from . import models
    from utils.logging import logger
    from rest_framework.response import Response
    class SuccessAPIView(APIView):
        # 不能认证,别人支付宝异步回调就进不来了
        # authentication_classes = [authentications.JWTAuthentication]
        # permission_classes = [IsAuthenticated]
        def patch(self, request, *args, **kwargs):
            # 默认是QueryDict类型,不能使用pop方法
            request_data = request.query_params.dict()
            # 必须将 sign、sign_type(内部有安全处理) 从数据中取出,拿sign与剩下的数据进行校验
            sign = request_data.pop('sign')
            result = alipay.verify(request_data, sign)
            if result:  # 同步回调:修改订单状态
                try:
                    out_trade_no = request_data.get('out_trade_no')
                    order = models.Order.objects.get(out_trade_no=out_trade_no)
                    if order.order_status != 1:
                        order.order_status = 1
                        order.save()
                except:
                    pass
                return APIResponse(0, '支付成功')
            return APIResponse(1, '支付失败')
    
        # 支付宝异步回调
        def post(self, request, *args, **kwargs):
            # 默认是QueryDict类型,不能使用pop方法
            request_data = request.data.dict()
            # 必须将 sign、sign_type(内部有安全处理) 从数据中取出,拿sign与剩下的数据进行校验
            sign = request_data.pop('sign')
            result = alipay.verify(request_data, sign)
            # 异步回调:修改订单状态
            if result and request_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED" ):
                out_trade_no = request_data.get('out_trade_no')
                logger.critical('%s支付成功' % out_trade_no)
                try:
                    order = models.Order.objects.get(out_trade_no=out_trade_no)
                    if order.order_status != 1:
                        order.order_status = 1
                        order.save()
                except:
                    pass
                # 支付宝八次异步通知,订单成功一定要返回 success
                return Response('success')
            return Response('failed')
    
  • 相关阅读:
    Zepto源码分析-动画(fx fx_method)模块
    CSS3用法理解
    移动端网页meta设置和响应式
    javascript基础-正则表达式
    net core 2.x
    angular
    .net core 2.x
    .net core 2.x
    DDD
    DDD
  • 原文地址:https://www.cnblogs.com/bladecheng/p/11553687.html
Copyright © 2011-2022 走看看