• vue框架前后端分离项目之订单表、立即付款接口及前端、支付成功回调等相关内容-131


    1 订单表设计

    1 两张表
    -一个订单可能包含多门课程
    -订单表:订单号,订单生成时间,订单总价格。。。(订单跟订单详情是一对多的关系)
       -订单详情表:order,course,该课程的价格。。。
       
       
       
    from django.db import models

    from user.models import User
    from course.models import Course
    # 不同app之间的表,可以建立关联关系,导入使用
    #user = models.ForeignKey(User) # 不能用引号引起来

    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)
       # 咱们生成的订单号(唯一:分布式id生成方案:雪花算法,snowflake)
       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="支付方式")
       # 支付时间(付款成功的时候,等支付宝post回调,回掉回来以后,返回数据中有支付时间)
       pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
       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='创建时间')

       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__()  # 相当于没写

    2 立即付款接口(一堆校验,登录后)

    1 付款接口需要登录后才能使用
    -自己写jwt的认证类
       -djangorestframwork-jwt 内置有个认证类,+ djangorestframwork的权限类也可以完成认证
      (目前使用这种)

    视图类

    class PayView(GenericViewSet,CreateModelMixin):
       queryset = Order.objects.all()
       serializer_class = PayOrderSerializer
       # 认证类,需要搭配权限类--->保证必须登录才能用
       # 只使用下面一个:(JSONWebTokenAuthentication)
       # 用户可以登录,也可以不登录,
       # 如果登录了后续能之间获取request.user
       # 如果没有登录,就获取不到
       authentication_classes = [JSONWebTokenAuthentication,]
       permission_classes = [IsAuthenticated,]

       # 重写create,控制返回的数据
       def create(self, request, *args, **kwargs):
           ser=self.get_serializer(data=request.data,context={'request':request})
           ser.is_valid(raise_exception=True)
           #保存
           self.perform_create(ser)
           # ser.save() 跟跟上面一样

           pay_url=ser.context.get('pay_url')

           return APIResponse(msg='订单创建成功',pay_url=pay_url)

    序列化类

    class PayOrderSerializer(serializers.ModelSerializer):
       # 用户传入的是courses:[1,2,3]==转成课程对象==》[obj1,obj2,obj3]
       # courses:[1,2,3]---->PrimaryKeyRelatedField--->指定queryset,会去queryset中过滤,主键为[1,2,3]的课程
       courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), many=True, write_only=True)

       # user=serializers.PrimaryKeyRelatedField(queryset=User.objects.all(),many=False)
       class Meta:
           model = Order
           # 从前端传过来(订单号后端生成)
           '''
            {'subject':'python从入门到精通',
              total_amount:100,
              pay_type:1 (支付类型,目前只有支付宝),
              courses:[1,2,3] 课程id号
            }
          '''
           fields = ['subject', 'total_amount', 'pay_type', 'courses']
           extra_kwargs = {
               'total_amount': {'required': True, }
          }

       def _check_price(self, attrs):
           courses = attrs.get('courses')
           total = 0
           for course in courses:
               total += course.price
           if not total == attrs.get('total_amount'):
               raise MyException('钱数不合法')

       def _get_order_no(self):
           order_no=str(uuid.uuid4())
           return order_no.replace('-','')

       def _get_user(self):
           user=self.context.get('request').user
           return user

       def _gen_pay_url(self,attrs,order_no):

           order_string = alipay.api_alipay_trade_page_pay(
               out_trade_no=order_no,
               total_amount=float(attrs.get('total_amount')), # 转成浮点型
               subject=attrs.get('subject'),
               # 写在配置文件中
               return_url=settings.RETURN_URL,
               notify_url=settings.NOTIFY_URL
          )
           return gateway+order_string

       def _pre_create(self,attrs,user,order_no):
           # 把用户,订单状态,订单号,写入attrs
           attrs['order_status']=0
           attrs['user']=user
           attrs['out_trade_no']=order_no



       def validate(self, attrs):
           # 1)订单总价校验
           self._check_price(attrs)
           # 2)生成订单号
           order_no=self._get_order_no()
           # 3)支付用户:request.user
           user=self._get_user()
           # 4)支付链接生成
           pay_url=self._gen_pay_url(attrs,order_no)
           self.context['pay_url']=pay_url
           # 5)入库(两个表)的信息准备(重写create方法)
           self._pre_create(attrs,user,order_no)
           return attrs

       def create(self, validated_data):
           # 存两个表
           # course需要存detail表
           courses=validated_data.pop('courses')
           # 开启一个事务
           order=Order.objects.create(**validated_data)
           for course in courses:
               OrderDetail.objects.create(order=order,course=course,price=course.price,real_price=course.price)

           return order

     

    3 立即付款前端

    buy_now(course) {
      //没有登录,提示请先登录
      let token = this.$cookies.get('token')
      if (token) {
          //发送请求获取支付链接
          this.$axios({
                  method: 'post',
                  url: this.$settings.base_url + '/order/pay/',
                  data: {
                      subject: course.name,
                      total_amount: course.price,
                      pay_type: 1,
                      courses: [course.id,]
                  },
                  headers: {
                      authorization: 'jwt ' + token
                  }
              }
          ).then(item => {
              console.log(item.data)
              if (item.data.status == 0) {
                  open(item.data.pay_url, '_self')
              } else {
                  this.$message({
                      message: item.data.msg
                  })
              }
          })

      } else {
          this.$message({
              message: "请先登录"
          })
      }
    }

    4 支付成功get回调用户展示

    前端页面

    <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/Header"

       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);
                   if(!response.data.status==0){
                       console.log('暂未收到您的付款,请3s钟后再刷新该页面')
                  }
              }).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回调,携带一些参数过来,展示课程购买成功
    charset=utf-8&
    out_trade_no=fe2baf0893d1403a89773e1e0151b4b3&
    method=alipay.trade.page.pay.return&total_amount=99.00&
    sign=C%2BtM1IqF9QBB7N86m6sEkJDoe8nvGKEymOPst%2FhrOTqZvZdyRTbr37a%2BAkhjV6Co6ot64mwJDVvlraJqevYltjXvWNovcViYAXL3JNZ%2FUoOo91PIsgFMJsTgXSy2R%2FyQ7NAhRhIhxGhNs5JNzTLt2JINKcZ%2FiUxzM%2Bkz3Z1EbjgB0JaDoNgRs9Wpwqb1VT%2FwZnyOAuoxOBwhij2SeP2ZC5qZfjQ8gzglSiUzbrQplbT3ZCGu5NHE1h42Zs8r3PkyxhghoK2T8UC7suI2u7l94713L8vP5hnegxkB79fNd4DQEZ4hnTV5nWDXjXw5RP9ob%2FQthMCTGssPS9%2Flme0%2F7w%3D%3D&
    trade_no=2021011522001453300501032429&
    auth_app_id=2016092000554611&version=1.0&
    app_id=2016092000554611&
    sign_type=RSA2&seller_id=2088102176466324&
    timestamp=2021-01-15%2012%3A23%3A44

    后端接口

    class PaySuccess(APIView):
       def get(self,request,*args,**kwargs): # 咱们前端回调的
           out_trade_no=request.GET.get('out_trade_no')
           # 去数据库查询该订单是否已经支付完成
           order=Order.objects.filter(out_trade_no=out_trade_no,order_status=1).first()
           if order: # post回调回调完了,订单状态改了
               return APIResponse(msg='支付成功')
           else:
               return APIResponse(status=1,msg='暂未支付成功')

    5 支付成功post回调修改订单状态

    class PaySuccess(APIView):

       def post(self,request,*args,**kwargs): # 支付宝回调
           # 验签,通过,修改订单状态,返回给支付宝success
           try:
               result_data = request.data.dict()  # 一定不能忘
               out_trade_no = result_data.get('out_trade_no')
               signature = result_data.pop('sign') # 签名,必须验证签名
               from lib import al_pay
               result = al_pay.alipay.verify(result_data, signature)
               if result and result_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
                   # 完成订单修改:订单状态、流水号、支付时间
                   Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1)
                   # 完成日志记录
                   logger.warning('%s订单支付成功' % out_trade_no)
                   return Response('success')  # 必须返回给支付宝,否则支付宝会一直回调
               else:
                   logger.error('%s订单支付失败' % out_trade_no)
           except:
               pass
           return Response('failed')

     

  • 相关阅读:
    GOF23设计模式汇总
    获取表单提交MVC错误信息
    Spring.Net
    简单工厂、工厂方法和抽象工厂
    Json和JsonP
    mysql8无法用navicat连接(mysql8加密方式的坑)
    (4.16)mysql备份还原——物理备份之XtraBackup实践
    mysql如何下载历史版本?
    如何测试端口通不通(四种方法)
    linux移动复制删除命令
  • 原文地址:https://www.cnblogs.com/usherwang/p/14284303.html
Copyright © 2020-2023  润新知