zoukankan      html  css  js  c++  java
  • 支付宝二次封装、订单模块与表分析、订单模块接口分析、支付接口、重写序列化类的create方法、前后台回调接口配置、前台生成订单并跳转、前台支付成功页面、支付宝get回调参数、同步异步回调接口、上线前准备

    ## 1 支付宝二次封装

    ```python
    al_pay
    -pem
    -__init__.py
    -pay.py
    -setting.py
    #__init__.py
    from .pay import alipay,gateway
    #pay.py

    from alipay import AliPay
    from . import setting
    alipay = AliPay(
    appid=setting.APPID,
    app_notify_url=None, # the default notify path
    app_private_key_string=setting.APP_PRIVATE_KEY_STRING,
    # alipay public key, do not use your own public key!
    alipay_public_key_string=setting.ALIPAY_PUBLIC_KEY_STRING,
    sign_type=setting.SIGN_TYPE, # RSA or RSA2
    debug=setting.DEBUG # False by default
    )
    gateway=setting.GATEWAY
    # setting.py
    import os
    APPID="2016092000554611"
    APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(__file__),'pem','private_key.pem')).read()
    ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(__file__),'pem','al_public_key.pem')).read()
    SIGN_TYPE='RSA2'
    DEBUG=True
    GATEWAY='https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'
    ```



    ## 2 订单模块与表分析

    ```python
    # 订单表分析
    -订单表
    -订单详情

    from django.db import models
    from django.db import models
    from user.models import User
    from course.models import Course
    # 订单表
    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)
    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="支付时间")
    # 一个用户可以下多个订单,一个订单只属于一个用户,一对多的关系,关联字段写在多个一方,写在order方
    user = models.ForeignKey(User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    updated_time = models.DateTimeField(auto_now=True, 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.SET_NULL, db_constraint=False, verbose_name="课程",null=True)
    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__()
    ```

    ## 3 订单模块接口分析

    ```python
    # 1 支付接口(生成订单,,生成支付连接,返回支付连接)
    -order表和orderdetail表插入数据,重写create方法
    -生成订单号(uuid)
    -登录后才能做(jwt认证)
    -当前登录用户就是下单用户,存到order表中
    -下了三个课程,总价格100,前端提交的价格是99,异常处理
    '''
    #1)订单总价校验
    # 2)生成订单号
    # 3)支付用户:request.user
    # 4)支付链接生成
    # 5)入库(两个表)的信息准备
    '''
    # 2 支付宝异步回调的post接口(验证签名,修改订单状态)
    # 3 当支付宝get回调前端,vue组件一创建,立马向后端发一个请求(get)
    ```

    ## 4 支付接口

    ```python
    # 1 前端传什么格式数据
    -{course:[1,2,3],total_amount:100,subject:xx商品,pay_type:1,}

    # 2 后端接到数据要校验
    -course:[1,2,3]===》course:[obj1,obj2,obj3]
    -在序列化类中: course=serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
    # 3 在序列化类的validate中处理一堆逻辑
    '''
    # 1)订单总价校验:
    -一个个课程价格取出来累加看是否等于传入的总价格
    # 2)生成订单号
    -通过uuid生成
    # 3)支付用户:request.user
    -通过视图和序列化类之间的桥梁context对象传递
    -重新视图类中的create方法,把request对象放入context
    -self.context.get('request').user
    # 4)支付链接生成
    -导入封装的支付宝支付,生成
    order_string = alipay.api_alipay_trade_page_pay (
    out_trade_no=out_trade_no,
    total_amount=total_amout,
    subject=subject,
    return_url=settings.RETURN_URL, # get回调,前台地址
    notify_url=settings.NOTIFY_URL # post回调,后台地址
    )
    # 5)入库(两个表)的信息准备
    -把user放入attrs中
    -把订单号,放入attrs中
    attrs['user']=user
    attrs['out_trade_no']=out_trade_no
    self.context['pay_url']=pay_url
    '''

    # 4 重写序列化类的create方法
    -把course_list弹出来
    -order=models.Order.objects.create(**validated_data)
    ```

    ```python
    # urls.py
    router = SimpleRouter()
    router.register('pay', views.PayView, 'pay')
    urlpatterns = [
    path('', include(router.urls)),
    ]

    # views.py
    class PayView(GenericViewSet,CreateModelMixin):
    authentication_classes = [JSONWebTokenAuthentication,]
    permission_classes = [IsAuthenticated,]
    queryset = models.Order.objects.all()
    serializer_class = serializer.OrderSerializer

    # 重写create方法
    def create(self, request, *args, **kwargs):
    serializer = self.get_serializer(data=request.data,context={'request':request})
    serializer.is_valid(raise_exception=True)
    self.perform_create(serializer)
    return Response(serializer.context.get('pay_url'))

    # serializer.py
    class OrderSerializer(serializers.ModelSerializer):
    # 前端传什么数据过来{course:[1,2,3],total_amount:100,subject:xx商品,pay_type:1,}
    # user字段需要,但是不是传的,使用了jwt


    # 需要把course:[1,2,3] 处理成 course:[obj1,obj2,obj3]

    # 课时:[1,4,6,]===>课时:[obj1,obj4,obj6,]
    # course=serializers.CharField()
    course=serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)

    class Meta:
    model = models.Order
    fields = ['total_amount','subject','pay_type','course']
    extra_kwargs={
    'total_amount':{'required':True},
    'pay_type': {'required': True},
    }


    def _check_price(self,attrs):
    total_amount=attrs.get('total_amount')
    course_list=attrs.get('course')
    total_price=0
    for course in course_list:
    total_price+=course.price
    if total_price!=total_amount:
    raise ValidationError('价格不合法')
    return total_amount

    def _gen_out_trade_no(self):
    import uuid
    return str(uuid.uuid4()).replace('-','')

    def _get_user(self):
    # 需要request对象(需要视图通过context把reuqest对象传入。重写create方法)
    request=self.context.get('request')
    return request.user

    def _gen_pay_url(self,out_trade_no,total_amout,subject):
    # total_amout是Decimal类型,识别不了,需要转换成float类型
    from luffyapi.libs.al_pay import alipay,gateway
    order_string = alipay.api_alipay_trade_page_pay (
    out_trade_no=out_trade_no,
    total_amount=float(total_amout),
    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):
    attrs['user']=user
    attrs['out_trade_no']=out_trade_no

    self.context['pay_url']=pay_url
    def validate(self, attrs):
    '''
    # 1)订单总价校验
    # 2)生成订单号
    # 3)支付用户:request.user
    # 4)支付链接生成
    # 5)入库(两个表)的信息准备
    '''
    # 1)订单总价校验
    total_amout = self._check_price(attrs)
    # 2)生成订单号
    out_trade_no=self._gen_out_trade_no()
    # 3)支付用户:request.user
    user=self._get_user()
    # 4)支付链接生成
    pay_url=self._gen_pay_url(out_trade_no,total_amout,attrs.get('subject'))
    # 5)入库(两个表)的信息准备
    self._before_create(attrs,user,pay_url,out_trade_no)
    return attrs
    def create(self, validated_data):
    course_list=validated_data.pop('course')
    order=models.Order.objects.create(**validated_data)
    for course in course_list:
    models.OrderDetail.objects.create(order=order,course=course,price=course.price,real_price=course.price)

    return order
    ```



    ## 5 前后台回调接口配置

    ```python
    # dev.py
    # 上线后必须换成公网地址
    # 后台基URL
    BASE_URL = 'http://127.0.0.1:8000'
    # 前台基URL
    LUFFY_URL = 'http://127.0.0.1:8080'
    # 支付宝同步异步回调接口配置
    # 后台异步回调接口
    NOTIFY_URL = BASE_URL + "/order/success/"
    # 前台同步回调接口,没有 / 结尾
    RETURN_URL = LUFFY_URL + "/pay/success"
    ```



    ## 6 前台生成订单并跳转

    ```python
    # FreeCourse.vue
    <span class="buy-now" @click="buy_now(course)">立即购买</span>
    # script
    buy_now(course) {
    let token = this.$cookies.get('token')
    if (!token) {
    this.$message({
    message: "您还没有登录,请先登录",
    })
    return false
    }
    this.$axios({
    method: 'post',
    url: this.$settings.base_url + '/order/pay/',
    data: {
    "total_amount": course.price,
    "subject": course.name,
    "pay_type": 1,
    "course": [
    course.id,
    ]
    },
    headers: {Authorization: 'jwt ' + token}
    }
    ).then(response => {
    console.log(response.data)
    let pay_url=response.data
    //前端发送get请求
    open(pay_url,'_self')
    }).catch(error => {
    })

    },
    ```



    ## 7 前台支付成功页面

    ```python
    # 路由
    {
    path: '/pay/success',
    name: 'PaySuccess',
    component: PaySuccess
    },

    #PaySuccess.vue
    <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>
    </div>
    </template>

    <script>
    import Header from "@/components/Head"

    export default {
    name: "Success",
    data() {
    return {
    result: {},
    };
    },
    created() {
    // url后拼接的参数:?及后面的所有参数 => ?a=1&b=2
    // console.log(location.search);

    // 解析支付宝回调的url参数
    let params = location.search.substring(1); // 去除? => a=1&b=2
    let items = params.length ? params.split('&') : []; // ['a=1', 'b=2']
    //逐个将每一项添加到args对象中
    for (let i = 0; i < items.length; i++) { // 第一次循环a=1,第二次b=2
    let k_v = items[i].split('='); // ['a', '1']
    //解码操作,因为查询字符串经过编码的
    if (k_v.length >= 2) {
    // url编码反解
    let k = decodeURIComponent(k_v[0]);
    this.result[k] = decodeURIComponent(k_v[1]);
    // 没有url编码反解
    // this.result[k_v[0]] = k_v[1];
    }

    }
    // 解析后的结果
    // console.log(this.result);


    // 把地址栏上面的支付结果,再get请求转发给后端
    this.$axios({
    url: this.$settings.base_url + '/order/success/' + location.search,
    method: 'get',
    }).then(response => {
    console.log(response.data);
    }).catch(() => {
    console.log('支付结果同步失败');
    })
    },
    components: {
    Header,
    }
    }
    </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>
    ```





    ### 支付宝get回调参数

    ```python
    charset=utf-8&
    out_trade_no=17fcf8cac17c442bbbc114df6004afff&
    method=alipay.trade.page.pay.return&
    total_amount=99.00&
    sign=WfK2beWFKvVaTHXpREi8HqZtFRH3JbeIvkliReYvfuhAsqaxHguARKtW6jUqUdZinm7ZSaYE1NrBRQa3%2BLquMk6uMnxE0i%2FTXIu4%2FmNTCEqSUlG8fTRPwC2%2BuU4nN1Ym0eM4puzAc2TUnEJnXCGKP9UxMifN3cjqR5BP%2B3RRngZSS4IQeogjurpfdiIolLzed%2FHTWbc4HqvWlWn9JuLmFGTtKHvRRKFr1hqq8Pj%2Fe3Al8kieDN9Q7JhEdC6F5ROo9rLlmUJtevkjI22oRScrfJl5hb%2BeYosxNg3WktmYKlF5vsKeZKKnLayAvKGoySLvaWk90x0LijHzzf2%2F8a9s3w%3D%3D&
    trade_no=2020072922001480160500851368&
    auth_app_id=2016092000554611&
    version=1.0&
    app_id=2016092000554611&
    sign_type=RSA2&
    seller_id=2088102176466324&
    timestamp=2020-07-29%2015%3A06%3A44
    ```



    ## 8 同步异步回调接口

    ```python
    class SuccessView(APIView):
    def get(self,request,*args,**kwargs):
    out_trade_no=request.query_params.get('out_trade_no')
    order=models.Order.objects.filter(out_trade_no=out_trade_no).first()
    if order.order_status==1:
    return Response(True)
    else:
    return Response(False)

    def post(self,request,*args,**kwargs):
    '''
    支付宝回调接口
    '''
    from luffyapi.libs.al_pay import alipay
    from luffyapi.utils.logger import log
    data = request.data
    out_trade_no=data.get('out_trade_no',None)
    gmt_payment=data.get('gmt_payment',None)
    signature = data.pop("sign")
    # 验证签名
    success = alipay.verify(data, signature)
    if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
    models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1,pay_time=gmt_payment)
    log.info('%s订单支付成功'%out_trade_no)
    return Response('success')
    else:
    log.info('%s订单有问题' % out_trade_no)
    return Response('error')
    ```

    ## 9 上线前准备

    ```python
    # 前端执行 npm run build 把你写的vue代码编译成html,css,js

    # 后端代码
    -修改setting中的pro.py
    -项目根路径新建一个manage_pro.py(把原来的manage.py复制改动上线的配置文件)
    -wsgi.py:改成线上的dev
    ```
  • 相关阅读:
    POJ 1236 Network of Schools(tarjan算法 + LCA)
    Hrbustoj 2266 Legendary Weights(辗转相除求最大公约数)
    纯虚函数的学习和使用
    完全背包(内含不能恰好装满的情况)
    多重背包并判断能否装满(附01完全背包思想)
    UVA 796 Critical Links (tarjan算法求割边)
    poj 2594 Treasure Exploration(最小路径覆盖,可重点)
    poj 3020 Antenna Placement (最小路径覆盖)
    ZOJ 1642
    Playground
  • 原文地址:https://www.cnblogs.com/0B0S/p/13618166.html
Copyright © 2011-2022 走看看