【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. 加入购物车:
2.购物车页面:
3.点击去结算,跳转到订单页面
二、订单模块
确认订单页面
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.页面展示
购物车页面点击去结算后,进入订单页面
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.页面展示
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("-",".")}}
// 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.页面展示
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.登录后,注册沙箱应用
2.配置回调地址以及应用公钥
3.通过本地公钥,获得支付宝应用公钥
4.在项目中配置
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
2.点击链接地址,跳转到支付宝平台
3.使用沙箱测试账号登录,进入支付页面
4.点击付款
5.完成后,自动跳转平台支付成功的地址
6.取消订单⭐
通过celery设置定时任务,定时检查订单状态。在订单超时未支付时,修改订单状态为取消订单状态。
参考:【Django+Vue3 线上教育平台项目实战】Celery赋能:优化订单超时处理与自动化定时任务调度
-
- 字段:id、user(外键)、course(外键)、is_checked(是否选中)