• day98-luff项目-支付宝二次封装、订单模块与表分析、订单模块接口分析、支付接口、前后台回调接口配置、前台生成订单并跳转、前台支付成功页面、支付宝get回调参数、同步异步回调接口、上线前准备


    昨日回顾

    # 1 区间过滤:
    	-自己写过滤类
        -使用django-filter
        	-在视图类中配置filter_backends=[DjangoFilterBackend]
            -写一个类,继承FilterSet
    	        min_price = filters.NumberFilter(field_name='price', lookup_expr='gt')
    	    	max_price = filters.NumberFilter(field_name='price', lookup_expr='lt')
            -在视图类中配置:filter_class = CourseFilterSet
            
    # 2 章节群查
    		-群查接口+过滤
            filter_backends = [DjangoFilterBackend]
        	filter_fields = ['course']
    # 3 为了解耦合,把搜索接口单独写的
    	
    # 4 支付宝支付
    

    今日内容

    1 支付宝二次封装

    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 订单模块与表分析

    # 订单表分析
    	-订单表
        -订单详情
        
    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 订单模块接口分析

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

    4 支付接口

    # 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)
    
    # 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 前后台回调接口配置

    # 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 前台生成订单并跳转

    # 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 前台支付成功页面

    # 路由
    {
            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;
            width: 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;
            width: 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;
            width: 140px;
            height: 42px;
            text-align: center;
            line-height: 42px;
            cursor: pointer;
            background: #ffc210;
            border-radius: 6px;
            font-size: 16px;
            color: #fff;
        }
    </style>
    

    支付宝get回调参数

    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 同步异步回调接口

    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 上线前准备

    # 前端执行 npm run build   把你写的vue代码编译成html,css,js
    
    # 后端代码
    	-修改setting中的pro.py
        -项目根路径新建一个manage_pro.py(把原来的manage.py复制改动上线的配置文件)
        -wsgi.py:改成线上的dev
    

    作业

    1 完成支付宝二次封装
    2 完成订单相关表
    3 完成支付宝支付接口
    4 前端支付和支付成功功能
    5 同步和异步回调接口
    6 线上准备
    7 (部分)尝试部署项目
    
  • 相关阅读:
    《解密腾讯海量服务之道》讲座笔记
    图解 Paxos 一致性协议
    向量时钟算法简介——本质类似MVCC
    logback配置
    Protocol Buffers简明教程
    分布式调用技术 RPC VS REST
    Dubbo和Spring Cloud微服务架构对比
    Spring Boot学习笔记
    relatedTarget、fromElement、toElement相关元素
    12个不可不知的Sublime Text应用技巧和诀窍
  • 原文地址:https://www.cnblogs.com/zdw20191029/p/14553269.html
Copyright © 2020-2023  润新知