【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

06-01 1196阅读

【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略


文章目录

  • 前言
  • 一、购物车模块
    • 1.后端核心逻辑
    • 2.前端页面代码
    • 3.操作流程及演示
    • 二、订单模块
      • 1.订单模块模型类设计
      • 1.展示订单信息
        • a.页面展示
        • b.前端核心代码
        • c.后端核心逻辑
        • 2.订单是否使用优惠券与积分
          • a.页面展示
          • b.前端核心代码
          • 3.订单支付方式
            • a.页面展示
            • b.前端核心代码
            • 4.提交订单(幂等性、事务性)⭐
            • 5.支付宝支付订单 ⭐
              • a.准备工作
              • b.前端获取支付链接地址
              • c.支付宝支付接口
              • d.操作流程及展示
              • 6.取消订单⭐

                前言

                    在构建线上教育平台的征途中,购物车与订单模块 是不可或缺的核心。本次,我们将聚焦于这两个模块的精简实现,包括购物车的添加、展示、修改功能,以及订单列表的展示、优惠券与积分的应用、支付宝支付的流程,特别是提交订单时,我们将深入探讨如何运用幂等性和事务性技术,确保订单数据的一致性和系统的稳定性,避免因网络延迟、重复点击等问题导致的订单异常。


                一、购物车模块

                  点击购买,加入购物车,判断购物车是否存在此课程,如果存在则特使不能重复添加,不存在加入购物车。

                1.后端核心逻辑

                购物车表

                • 字段:id、user(外键)、course(外键)、is_checked(是否选中)

                  添加购物车 、获取购物车列表信息、修改购物车选中状态:

                  # 0.购物车获取、添加、修改选中状态
                  class CartView(APIView):
                      # 获取购物车列表,传参 userid
                      def get(self, request):
                          # 获取参数
                          userid = request.GET.get('userid')
                          carts = CartsModel.objects.filter(user_id=userid)
                          cartsSer = CartsSerializer(carts, many=True)
                          carts_list = cartsSer.data
                          return Response({"code":"200","carts":carts_list,"total":len(carts_list),"others":"待添加..."})
                      # post-加入购物车
                      def post(self, request):
                          # 获取参数
                          userid = request.data.get('userid')
                          courseid = request.data.get('courseid')
                          # 查询购物车表,是否已存在对应数据
                          cart = CartsModel.objects.filter(user_id=userid, course_id=courseid).first()
                          if cart is None:
                              # 查询用户表课程是否存在
                              ucourse = UserCourseModel.objects.filter(user_id=userid, course_id=courseid).first()
                              if ucourse:
                              	# 用户表已有此课程,不能重复购买
                                  return Response({"code":10010,"message":"该课程已经购买,不可重复购买!"})
                              # 不存在,添加购物车数据
                              # cartInfo = {"user": userid, "course": courseid}
                              cartSer = CartsSerializer(data=request.data)
                              if cartSer.is_valid():
                                  cartSer.save()
                                  return Response({"message":"添加购物车成功!","code":"200"})
                              else:
                                  print(cartSer.errors)
                                  return Response({"message":cartSer.errors,"code":"1001"})
                          else:
                              # 购物车已存在此信息,不能重复添加购物车
                              return Response({"message":"购物车已存在此信息,切勿重复添加!","code":"1001"})
                      # 修改购物车状态
                      def patch(self, request):
                          # 获取参数-购物车id,购物车选中状态
                          cartid = request.data.get('course_id')
                          selected = request.data.get('selected')
                          print(cartid,selected)
                          CartsModel.objects.filter(id=cartid).update(is_checked=selected)
                          return Response({"message":"patch方法修改状态成功!","code":"200"})
                  

                  2.前端页面代码

                  购物车页面 src\views\Cart.vue:

                    

                  我的购物车

                  {{cart.course_list.length}}门,已选择{{cart.selected_course_total}}
                  我的订单列表
                  全选
                  课程
                  金额
                  操作
                  【{{course_info.course.name}}】 {{course_info.course.describe}}
                  {{course_info.course.price.toFixed(2)}}
                  {{course_info.course.price.toFixed(2)}}
                  总计金额: {{cart.total_price.toFixed(2)}}
                  去结算
                  import cart from "../api/cart";
                  let token = sessionStorage.access_token || localStorage.access_token;
                  cart.list(token)
                  

                  src\api\cart.js

                  import { reactive } from "vue";
                  import http from "../http";
                  import store from "../store";
                  import router from "../router";
                  const cart = reactive({
                    course_list: [],  // 购物车中的商品列表
                    total_price: 0,  // 购物车中的商品总价格
                    selected_course_total: 0, // 购物车中被勾选商品的数量
                    checked: false,  // 购物车中是否全选商品了
                    add(course_id, token) {
                      // 添加商品到购物车
                      return http.post("/carts/cart/", {
                        course_id: course_id
                      }, {
                        // 因为当前课程端添加课程商品到购物车必须登录,所以接口操作时必须发送jwt
                        headers: {
                          Authorization: "Bearer " + token,
                        }
                      })
                    },
                    list(token) {
                      // 购物车商品列表
                      
                      // 本地浏览器获取userid
                      const userid = localStorage.getItem('userid')
                      return http.get("/carts/cart/?userid="+userid, {
                        headers: {
                          Authorization: "Bearer " + token,
                        }
                      }).then(response => {
                        if (response.data.carts) {
                          this.course_list = response.data.carts;
                          this.calc();
                        } else {
                          ElMessage.error("当前购物车没有任何商品,请购物后再继续操作!");
                          router.push("/");
                        }
                        return response
                      })
                    },
                    calc() {
                      // 计算当前购物车中的商品总价格和勾选商品的总数
                      let total_price = 0;  // 临时设置一个变量用于累计总价格
                      let select_total = 0; // 临时设置一个变量用于累计勾选商品的数量
                      this.course_list.forEach(course => {
                        // 累计当前购物车中有多少商品课程被勾选了
                        if (course.is_checked) {
                          console.log("累计当前购物车中有多少商品课程被勾选了********");
                          // 统计当前课程的总价格
                          total_price += parseFloat(course.course.price);
                          select_total += 1;
                        }
                      })
                      this.total_price = total_price;
                      this.selected_course_total = select_total;
                      this.checked = this.selected_course_total === this.course_list.length;
                    },
                    select(course_id, selected, token) {
                      // 切换商品课程的勾选状态
                      return http.patch("/carts/cart/", {
                        course_id: course_id,
                        selected: selected,
                      }, {
                        // 因为当前课程端添加课程商品到购物车必须登录,所以接口操作时必须发送jwt
                        headers: {
                          Authorization: "Bearer " + token,
                        }
                      }).then(response => {
                        // 重新计算被勾选的商品数量
                        this.calc();
                      })
                    },
                    select_all(selected, token) {
                      // 切换购物车对应商品课程的全选状态
                      return http.put("/cart/", {
                        selected,
                      }, {
                        headers: {
                          Authorization: "Bearer " + token,
                        }
                      }).then(response => {
                        this.calc();
                      })
                    },
                    delete_course(key, token) {
                      // 从购物车中删除商品课程
                      let course_id = this.course_list[key].id;
                      return http.delete("/cart/", {
                        params: {
                          course_id,  // course_id: course_id,的简写
                        },
                        headers: {
                          Authorization: "Bearer " + token,
                        }
                      }).then(response => {
                        this.course_list.splice(key, 1);
                        // 通知vuex更新购物车中商品总数
                        store.commit("set_cart_total", this.course_list.length);
                        this.calc();
                      })
                    },
                  })
                  export default cart;
                  

                  3.操作流程及演示

                  1. 加入购物车:

                  【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                  2.购物车页面:

                  【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                  3.点击去结算,跳转到订单页面


                  二、订单模块

                  确认订单页面

                  【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                  1.订单模块模型类设计

                  订单模块:订单表、订单详情表、优惠券表、用户优惠券表、积分记录表

                  # 1.订单表
                  class OrdersModel(models.Model):
                      orderno = models.CharField(max_length=255,default="",primary_key=True)
                      user = models.ForeignKey(UsersModel, on_delete=models.CASCADE, verbose_name='用户id')  # 注意:这里通常使用外键关联User模型
                      pay_status = models.IntegerField(choices=((1, '生成'), (2, '已支付'), (3, '支付失败')), verbose_name='支付状态')
                      pay_type = models.IntegerField(choices=((1, '支付宝'), (2, '微信'), (3, '网银')), verbose_name='支付类型')
                      orders_status = models.IntegerField(
                          choices=((1, '生成'), (2, '已支付'), (3, '支付失败'), (4, '退款'), (5, '评价'), (6, '已经完成'), (7, '取消')),
                          verbose_name='订单状态')
                      transaction = models.CharField(max_length=50, verbose_name='流水号')
                      score = models.IntegerField(default=0,verbose_name="积分")
                      coupon_id = models.IntegerField(default=0,verbose_name="优惠券id")
                      coupon_money = models.IntegerField(default=0,verbose_name="优惠金额")
                      total_money = models.IntegerField(default=0,verbose_name="总金额")
                      pay_money = models.IntegerField(default=0,verbose_name="实际支付金额")
                      def __str__(self):
                          return f'订单ID: {self.id}'
                      class Meta:
                          verbose_name = '订单表'
                          verbose_name_plural = '订单表'
                          db_table = 'orders'
                  # 2.订单详情表
                  class OrdersDetailModel(models.Model):
                      user = models.ForeignKey(UsersModel, on_delete=models.CASCADE)
                      orders = models.ForeignKey(OrdersModel, on_delete=models.CASCADE)
                      course = models.ForeignKey(CourseModel, on_delete=models.CASCADE)
                      name = models.CharField(max_length=255)  # course被修改时,下订单时的属性不改变
                      picurl = models.CharField(max_length=100, default='')# course被修改时,下订单时的属性不改变
                      price = models.FloatField()# course被修改时,下订单时的属性不改变
                      comment_status = models.IntegerField(default=0,verbose_name="评价状态")
                      def __str__(self):
                          return f'订单详情ID: {self.id}'
                      class Meta:
                          verbose_name = '订单详情表'
                          verbose_name_plural = '订单详情表'
                          db_table = "order_detail"
                  # 3.优惠券表:id、name、man满、jian减、开始时间、结束时间、总张数、已发
                  class CouponModel(models.Model):
                      name = models.CharField(max_length=255,default="优惠券默认名")
                      coupon_full = models.IntegerField(verbose_name="价格满-",default=0)
                      coupon_reduce = models.IntegerField(verbose_name="价格减少-",default=0)
                      start_time = models.DateTimeField(verbose_name="开始时间")
                      end_time = models.DateTimeField(verbose_name="结束时间")
                      count = models.IntegerField(verbose_name="优惠券总量",default=100)
                      count_issued = models.IntegerField(verbose_name="已发出的优惠券数量",default=0)
                      def __str__(self):
                          return self.name
                      class Meta:
                          verbose_name = "优惠券表"
                          verbose_name_plural = "优惠券表"
                          db_table = "coupon"
                  # 4.用户优惠券表:user、优惠券、name、man满、jian减、开始时间、结束时间、使用状态
                  class UserCouponModel(models.Model):
                      user = models.ForeignKey(UsersModel, on_delete=models.CASCADE)
                      coupon = models.ForeignKey(CouponModel, on_delete=models.CASCADE)
                      name = models.CharField(max_length=255,default="用户优惠券的默认名")
                      coupon_full = models.IntegerField(verbose_name="价格满-",default=0)
                      coupon_reduce = models.IntegerField(verbose_name="价格减少-",default=0)
                      start_time = models.DateTimeField(verbose_name="开始时间")
                      end_time = models.DateTimeField(verbose_name="结束时间")
                      status = models.CharField(max_length=100,default="未使用")
                      def __str__(self):
                          return self.name
                      class Meta:
                          verbose_name = "用户优惠券表"
                          verbose_name_plural = "用户优惠券表"
                          db_table = "usercoupon"
                  # 5.积分记录表
                  class ScoreRecordModel(models.Model):
                      user = models.ForeignKey(UsersModel, on_delete=models.CASCADE)
                      types = models.IntegerField(default=1) #1+积分 2-积分
                      score = models.IntegerField(default=0)
                      descp = models.CharField(max_length=255, default='')
                      def __str__(self):
                          return self.descp
                      class Meta:
                          verbose_name = "积分记录表"
                          verbose_name_plural = "积分记录表"
                          db_table = "score_record"
                  

                  1.展示订单信息

                  a.页面展示

                  购物车页面点击去结算后,进入订单页面

                  【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                  b.前端核心代码

                  订单页面 — 展示订单信息 src\views\Order.vue

                  {course_info}} -->
                  【{{course_info.course.name}}】{{course_info.course.describe}}

                  {{course_info.course.price.toFixed(2)}}

                  src\api\order.js

                    // 1.获取购物车中的勾选商品列表
                    get_selected(token) {
                      return http.get("/carts/preorder/?userid=1", {
                        headers: {
                          Authorization: "Bearer " + token,
                        }
                      }).then(response => {
                        console.log("response.data-----------获取购物车中的勾选商品列表---------------")
                        console.log(response.data)
                        if (response.data.cart) {
                          this.selected_course_list = response.data.cart;
                          this.calc_cart();
                          // this.get_coupon_list(token);
                          // 此用户拥有的优惠券
                          this.coupon_list = response.data.coupon
                          // 总积分
                          this.has_credit = response.data.score;
                          // 把用户拥有的积分与本次下单的最大对换积分进行比较,判断用户可以使用的最大数量积分
                          if (this.has_credit  
                  

                  c.后端核心逻辑

                  cart/views.py

                  # 1.订单前,信息
                  class PreOrderView(APIView):
                      def get(self, request):
                          # 获取userid
                          userid = request.GET.get('userid')
                          # 查询购物车中已经选中的记录
                          cart = CartsModel.objects.filter(user_id=userid,is_checked=True).all()
                          cartSer = CartsSerializer(cart, many=True)
                          # 查询用户的总积分
                          user = UsersModel.objects.filter(id=userid).first()
                          score = user.points
                          # 查询优惠券
                          coupons = UserCouponModel.objects.filter(user_id=userid)
                          couponsSer = UserCouponSerializer(coupons, many=True)
                          coupons_list = couponsSer.data
                          return Response({"code":"200","cart":cartSer.data,"score":score,"coupon":coupons_list})
                  

                  2.订单是否使用优惠券与积分

                  a.页面展示

                  【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                  【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                  b.前端核心代码

                  src\views\Order.vue

                  使用优惠券/积分

                  优惠券 ({{order.coupon_list.length}})
                  积分 ({{order.has_credit}})

                  暂无可用优惠券

                  • ¥{{Math.abs(coupon.sale)}}

                    {{coupon.sale.replace("*0.","")}}折

                    满{{coupon.coupon_full}}元可用

                    任意使用

                    适用于:{{coupon.name}}
                    有效期:{{coupon.start_time.split("T")[0].replaceAll("-",".")}}-{{coupon.end_time.split("T")[0].replaceAll("-",".")}}

                  使用积分:200

                  课程:{{course_info.course.name}} {{course_info.credit}}积分抵扣:{{parseInt(course_info.credit / order.credit_to_money)}}

                  本次订单最多可以使用{{order.max_use_credit}}积分,您当前拥有{{order.has_credit}}积分。({{order.credit_to_money}}积分=1元)

                  说明:每笔订单只能使用一次积分,并只有在部分允许使用积分兑换的课程中才能使用。

                    // 1.获取购物车中的勾选商品列表
                    get_selected(token) {
                      return http.get("/carts/preorder/?userid=1", {
                        headers: {
                          Authorization: "Bearer " + token,
                        }
                      }).then(response => {
                        console.log("response.data-----------获取购物车中的勾选商品列表---------------")
                        console.log(response.data)
                        if (response.data.cart) {
                          console.log(111111);
                          this.selected_course_list = response.data.cart;
                          this.calc_cart();
                          // 此用户拥有的优惠券
                          this.coupon_list = response.data.coupon
                          // 总积分
                          this.has_credit = response.data.score;
                          // 把用户拥有的积分与本次下单的最大对换积分进行比较,判断用户可以使用的最大数量积分
                          if (this.has_credit  {
                        original_total_price += course.course.price;
                        real_total_price += course.course.price;
                      })
                      this.total_price = original_total_price;
                      this.real_price = real_total_price;
                      this.max_use_credit = this.real_price;
                    },
                  

                  3.订单支付方式

                  a.页面展示

                  【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                  b.前端核心代码

                  选择支付方式

                  支付宝微信余额

                  4.提交订单(幂等性、事务性)⭐

                  在提交订单时,可能会遇到如下问题:

                  1.解决网络慢导致的频繁点击生成无效订单问题

                  • 问题描述: 由于网络延迟,用户可能在短时间内频繁点击生成订单按钮,导致生成多个无效订单。
                  • 解决方案: 通过 接口幂等性操作 来避免重复订单的生成。
                  • 实现流程:
                    • 1.前端准备: 在确认订单页面的onMounted生命周期钩子中,调用后端接口请求一个唯一的标识符(UUID)。
                    • 2.后端生成UUID: 后端接口接收到请求后,使用uuid.uuid4().hex()生成一个唯一的UUID,并将其存储在Redis中(可以设置一个合理的过期时间,如几分钟),然后将这个UUID返回给前端。
                    • 3.前端携带UUID生成订单: 用户点击生成订单按钮时,将之前获取的UUID作为参数一同发送到后端。
                    • 4.后端验证UUID: 后端接收到订单生成请求时,首先检查Redis中是否存在该UUID。
                      • 如果存在,说明是重复请求,直接返回“订单已生成,请勿重复操作”的提示,并从Redis中删除该UUID(可选,视业务逻辑而定)。
                      • 如果不存在,则认为是有效请求,继续订单生成流程,并在订单生成成功后,确保不将UUID存入Redis(因为是单次有效)。


                        2.解决订单与订单详情写入不一致问题

                        • 问题描述: 在生成订单的过程中,订单表已成功写入数据,但在写入订单详情表时发生错误,导致订单详情缺失,用户支付成功后可能无法获得相应的课程。
                        • 解决方案: 使用数据库事务来保证订单和订单详情的一致性。
                        • 实现流程:
                          • 1.引入事务支持: 确保你的数据库框架或ORM支持事务处理。例如,在Django中可以使用transaction模块,在SQLAlchemy中也有相应的事务管理。
                          • 2.开启事务: 在生成订单的接口逻辑中,首先开启一个数据库事务。这通常在数据库操作开始前进行。
                          • 3.执行订单和订单详情写入: 在事务的上下文中,先执行订单表的写入操作,然后执行订单详情表的写入操作。
                          • 4.异常处理与事务回滚: 如果在写入订单详情表时发生异常,立即捕获该异常,并执行事务回滚操作,以确保订单表中的数据也被撤销。这样可以保证数据库的一致性和完整性。
                          • 5.提交事务: 如果所有操作都成功完成,没有发生异常,则提交事务,使所有更改永久生效。
                            # 2.订单uid
                            class OrderKeysView(APIView):
                                def get(self, request):
                                    userid = request.GET.get('userid')
                                    uid = uuid.uuid1().hex
                                    key = str(uid) + str(userid)
                                    r.set_str(key,'111')
                                    return Response({"code":"200","orderuid":key})
                            import datetime
                            from django.db import transaction
                            # 3.提交订单
                            class OrderView(APIView):
                                @transaction.atomic()
                                def post(self, request):
                                    # 获取参数:用户id、支付方式...
                                    userid = request.data.get('userid')
                                    pay_type = request.data.get('pay_type')
                                    credit = request.data.get('credit') #// 当前用户选择抵扣的积分
                                    user_coupon_id = request.data.get('user_coupon_id')
                                    orderuid = request.data.get('orderuid')
                                    print("验证接口幂等性1")
                                    # ***验证接口幂等性***
                                    value = r.get_str(orderuid)
                                    if value:
                                        #删除
                                        r.delete_str(orderuid)
                                        # 生成订单号
                                        print("验证接口幂等性2")
                                        orderno = datetime.datetime.strftime(datetime.datetime.now(), '%Y%m%d%H%M%S')+str(userid)+str(random.randint(10000,99999))
                                        print("orderno" + str(orderno))
                                        # 开启事务
                                        # 创建保存点
                                        save_id = transaction.savepoint()
                                        try:
                                            # 判断优惠券id,若存在查询用户优惠券表
                                            couponmoney = 0
                                            if int(user_coupon_id)>0:
                                                user_coupon = UserCouponModel.objects.filter(id=user_coupon_id).first()
                                                user_coupon.status = "已使用"
                                                couponmoney = user_coupon.coupon_reduce #价格减少coupon_reduce
                                                user_coupon.save()
                                            # 添加订单表
                                            orders = OrdersModel.objects.create(user_id=userid,pay_type=pay_type,score=credit,coupon_id=user_coupon_id,coupon_money=0,transaction=orderno,pay_status=2,orders_status=1)
                                            # 根据用户查询选中购物车
                                            total_money = 0
                                            pay_money = 0
                                            # 遍历购物车,写入订单表,计算出总价格,删除购物车中商品
                                            carts = CartsModel.objects.filter(user_id=userid,is_checked=True).all()
                                            for cart in carts:
                                                total_money += float(cart.course.price)
                                                # 添加订单详情表
                                                OrdersDetailModel.objects.create(user_id=userid,orders_id=orders.id,course_id=cart.course.id,name=cart.course.name,picurl = cart.course.picurl,price = cart.course.price)
                                                # 删除购物车表
                                                # CartsModel.objects.filter(id=cart.id).delete()
                                            # 更新订单总价格---
                                            orders.total_money = total_money
                                            coupon = int(couponmoney) + int(credit) #总优惠金额
                                            if total_money > coupon:
                                                orders.pay_money = total_money - coupon
                                            else:
                                                orders.pay_money = 0
                                            orders.save()
                                            #如果使用积分,更新积分记录表,更新用户表中的总积分
                                            if int(credit) > 0:
                                                user = UsersModel.objects.filter(id=userid).first()
                                                user.points -= int(credit) #更新用户表总积分
                                                user.save()
                                                ScoreRecordModel.objects.create(user_id=userid,types=2,score=int(credit),descp="购买订单"+orderno+"时所用积分")
                                            # 提交事务
                                            # zset --- 把订单号 + score 加入队列
                                            times = int(time.time())+1800
                                            r.zset_zadd("orderno",times,orderno)
                                            transaction.savepoint_commit(save_id)
                                            return Response({"code":"200"})
                                        except:
                                            # 失败回滚
                                            transaction.savepoint_rollback(save_id)
                                            return Response({"code": "1001", "message": "订单生成失败!"})
                            

                            5.支付宝支付订单 ⭐

                            支付宝开放平台:https://open.alipay.com/


                            操作流程:

                            1.支付宝开放平台注册账号,注册应用

                            2.在应用中配置回调地址,生成公钥私钥,在支付宝上配置

                            3.获取支付链接

                            4.点击链接跳转到支付宝平台,用测试账号登录,输入支付密码。

                            5.支付成功后支付宝会根据配置的回调地址回调接口

                            6.在接口中进行业务的处理

                            • 获取参数
                            • 验证签名
                            • 更新订单表中订单状态、支付状态、流水号

                              a.准备工作

                              1.登录后,注册沙箱应用

                              【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                              2.配置回调地址以及应用公钥

                              【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                              3.通过本地公钥,获得支付宝应用公钥

                              【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                              4.在项目中配置

                              【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                              5.支付宝 支付工具类

                              • tools/pay.py
                              • tools/common.py - - - get_alipay()
                                # tools/pay.py
                                from datetime import datetime
                                from Crypto.PublicKey import RSA
                                from Crypto.Signature import PKCS1_v1_5
                                from Crypto.Hash import SHA256
                                from urllib.parse import quote_plus
                                from urllib.parse import urlparse, parse_qs
                                from base64 import decodebytes, encodebytes
                                import json
                                class AliPay(object):
                                    """
                                    支付宝支付接口(PC端支付接口)
                                    """
                                    def __init__(self, appid, app_notify_url, app_private_key_path,
                                                 alipay_public_key_path, return_url, debug=False):
                                        self.appid = appid
                                        self.app_notify_url = app_notify_url
                                        self.app_private_key_path = app_private_key_path
                                        self.app_private_key = None
                                        self.return_url = return_url
                                        with open(self.app_private_key_path) as fp:
                                            self.app_private_key = RSA.importKey(fp.read())
                                        self.alipay_public_key_path = alipay_public_key_path
                                        with open(self.alipay_public_key_path) as fp:
                                            self.alipay_public_key = RSA.importKey(fp.read())
                                        if debug is True:
                                            self.__gateway = "https://openapi.alipaydev.com/gateway.do"
                                        else:
                                            self.__gateway = "https://openapi.alipay.com/gateway.do"
                                    def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs):
                                        biz_content = {
                                            "subject": subject,
                                            "out_trade_no": out_trade_no,
                                            "total_amount": total_amount,
                                            "product_code": "FAST_INSTANT_TRADE_PAY",
                                            # "qr_pay_mode":4
                                        }
                                        biz_content.update(kwargs)
                                        data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url)
                                        return self.sign_data(data)
                                    
                                     #查询接口
                                    def query_pay(self, out_trade_no,return_url=None, **kwargs):
                                        biz_content = {
                                            "out_trade_no": out_trade_no,
                                           
                                            # "product_code": "FAST_INSTANT_TRADE_PAY",
                                            # "qr_pay_mode":4
                                        }
                                        biz_content.update(kwargs)
                                        data = self.build_body("alipay.trade.query", biz_content, self.return_url)
                                        return self.sign_data(data)
                                    
                                    #转账接口
                                    def deposit(self,out_biz_no,trans_amount,title,payee_info,return_url=None,**kwargs):
                                          biz_content = {
                                              "out_biz_no":out_biz_no,
                                              "trans_amount":trans_amount,
                                              "biz_scene":"DIRECT_TRANSFER",
                                              "product_code":"TRANS_ACCOUNT_NO_PWD",
                                              "order_title":title,
                                              "payee_info":payee_info
                                          }
                                          biz_content.update(kwargs)
                                          data = self.build_body("alipay.fund.trans.uni.transfer", biz_content, self.return_url)
                                          return self.sign_data(data)
                                    #转账接口
                                    def refund(self,trade_no,refund_amount,return_url=None,**kwargs):
                                          biz_content = {
                                              "trade_no":trade_no,
                                              "refund_amount":refund_amount,
                                          }
                                          biz_content.update(kwargs)
                                          data = self.build_body("alipay.trade.refund", biz_content, self.return_url)
                                          return self.sign_data(data)
                                    def build_body(self, method, biz_content, return_url=None):
                                        data = {
                                            "app_id": self.appid,
                                            "method": method,
                                            "charset": "utf-8",
                                            "sign_type": "RSA2",
                                            "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                            "version": "1.0",
                                            "biz_content": biz_content
                                        }
                                        if return_url is not None:
                                            data["notify_url"] = self.app_notify_url
                                            data["return_url"] = self.return_url
                                        return data
                                    def sign_data(self, data):
                                        data.pop("sign", None)
                                        # 排序后的字符串
                                        unsigned_items = self.ordered_data(data)
                                        unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items)
                                        sign = self.sign(unsigned_string.encode("utf-8"))
                                        # ordered_items = self.ordered_data(data)
                                        quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items)
                                        # 获得最终的订单信息字符串
                                        signed_string = quoted_string + "&sign=" + quote_plus(sign)
                                        return signed_string
                                    def ordered_data(self, data):
                                        complex_keys = []
                                        for key, value in data.items():
                                            if isinstance(value, dict):
                                                complex_keys.append(key)
                                        # 将字典类型的数据dump出来
                                        for key in complex_keys:
                                            data[key] = json.dumps(data[key], separators=(',', ':'))
                                        return sorted([(k, v) for k, v in data.items()])
                                    def sign(self, unsigned_string):
                                        # 开始计算签名
                                        key = self.app_private_key
                                        signer = PKCS1_v1_5.new(key)
                                        signature = signer.sign(SHA256.new(unsigned_string))
                                        # base64 编码,转换为unicode表示并移除回车
                                        sign = encodebytes(signature).decode("utf8").replace("\n", "")
                                        return sign
                                    def _verify(self, raw_content, signature):
                                        # 开始计算签名
                                        key = self.alipay_public_key
                                        signer = PKCS1_v1_5.new(key)
                                        digest = SHA256.new()
                                        digest.update(raw_content.encode("utf8"))
                                        if signer.verify(digest, decodebytes(signature.encode("utf8"))):
                                            return True
                                        return False
                                    def verify(self, data, signature):
                                        if "sign_type" in data:
                                            sign_type = data.pop("sign_type")
                                        # 排序后的字符串
                                        unsigned_items = self.ordered_data(data)
                                        message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items)
                                        return self._verify(message, signature)
                                
                                # tools/common.py
                                from .pay import AliPay
                                def get_alipay():
                                    # 初始化支付实例
                                    # 公共参数
                                    # 沙箱环境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info
                                    app_id = "1111111111111"  # APPID (沙箱应用)
                                    # 支付完成后,支付偷偷向这里地址发送一个post请求,识别公网IP,如果是 192.168.20.13局域网IP ,支付宝找不到,def page2() 接收不到这个请求
                                    notify_url = "http://127.0.0.1:8000/carts/pay/"
                                    # 支付完成后,跳转的地址。
                                    return_url = "http://127.0.0.1:8000/carts/pay/"
                                    merchant_private_key_path = "tools/keys/private.txt"  # 应用私钥
                                    alipay_public_key_path = "tools/keys/public.txt"  # 支付宝公钥
                                    alipay = AliPay(
                                        appid=app_id,
                                        app_notify_url=notify_url,
                                        return_url=return_url,
                                        app_private_key_path=merchant_private_key_path,
                                        alipay_public_key_path=alipay_public_key_path,  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥
                                        debug=True,  # 默认False,
                                    )
                                    return alipay
                                

                                b.前端获取支付链接地址

                                  get_pay_link(order_id) {
                                    // 获取支付宝支付链接地址
                                    return http.post("/carts/pay/", {
                                	    order:this.order,
                                	    pay_money:this.pay_money
                                    }).then(response => {
                                      window.open(response.data, "_blank");
                                    })
                                  },
                                

                                c.支付宝支付接口

                                # 4.支付宝接口 支付
                                class PayView(APIView):
                                    def post(self, request):
                                        # 获取订单号
                                        orderno = 10
                                        pay_money = 100
                                        pay = get_alipay()
                                        query_params = pay.direct_pay(
                                            subject="订单支付",  # 商品简单描述
                                            out_trade_no=str(orderno),  # 用户购买的商品订单号(每次不一样) 20180301073422891
                                            total_amount=float(pay_money),  # 交易金额(单位: 元 保留俩位小数)
                                        )
                                        payUrl = "https://openapi-sandbox.dl.alipaydev.com/gateway.do?{0}".format(query_params)
                                        return Response({"url":payUrl})
                                        
                                    def get(self, request):
                                        # 获取参数
                                        data = request.GET
                                        print("data----------" + str(data))
                                        rdata = {k:v for k,v in data.items()}
                                        print("rdata++++++++++++" + str(rdata))
                                        # 验证签名
                                        sign = rdata.pop('sign')
                                        pay = get_alipay()
                                        flag = pay.verify(rdata,sign)
                                        print(flag)
                                        # 业务逻辑
                                        if flag:
                                            # 更新订单表
                                            orderno = data['out_trade_no']
                                            # 支付宝流水号
                                            transaction_no = data['trade_no']
                                            # 调用支付宝查询接口查询
                                            flag = True
                                            if flag == True:
                                                # 通过订单号查询订单表
                                                orders = OrdersModel.objects.filter(orderno=orderno).first()
                                                orders.status=2 #状态
                                                orders.orders_status = 2 #支付状态
                                                orders.transaction_no = transaction_no
                                                orders.save()
                                                # 把成功的加入到成功队列中
                                                r.list_push("ordersuccess",orderno)
                                                return HttpResponseRedirect("http://127.0.0.1:3000/")
                                            else:
                                                orders = OrdersModel.objects.filter(orderno=orderno).first()
                                                orders.status=3
                                                orders.orders_status = 3
                                                orders.transaction_no = transaction_no
                                                orders.save()
                                                # 把成功的加入到成功队列中
                                                r.list_push("orderfail",orderno)
                                                return HttpResponseRedirect("http://127.0.0.1:3000/error")
                                

                                d.操作流程及展示

                                1.获取url支付链接地址,为了测试方便这里使用postman

                                【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                                2.点击链接地址,跳转到支付宝平台

                                【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                                3.使用沙箱测试账号登录,进入支付页面

                                【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                                【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                                4.点击付款

                                【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                                5.完成后,自动跳转平台支付成功的地址

                                【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

                                6.取消订单⭐

                                    通过celery设置定时任务,定时检查订单状态。在订单超时未支付时,修改订单状态为取消订单状态。

                                    参考:【Django+Vue3 线上教育平台项目实战】Celery赋能:优化订单超时处理与自动化定时任务调度


                                【Django+Vue3 线上教育平台项目实战】购物车与订单模块的精简实现与数据安全策略

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码