【一】订单相关表设计
- Order 订单表
- OrderDetail 订单详情表
from django.db import models
from luffyCity.apps.course.models import Course
from luffyCity.apps.user.models import User
# Order 订单表
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="支付时间") # 支付宝返回的时间
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)
# OrderDetail 订单详情表
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__()
- 数据库迁移
python ../../manage.py makemigrations
python ../../manage.py migrate
【二】下单接口
【1】分析
(1)前端
-
前端传入数据
- 在订单表中插入记录,生成支付链接,返回给前端
-
前端传入数据
{'subject':'某个课程', 'total_amount':100, 'pay_type':1, 'courses':[1,2,3]}
(2)后端
-
后端做的事情--要保存,要校验
- 序列化类来做
-
1)订单总价校验
-
2)生成订单号:唯一的
-
3)获取支付用户:request.user
-
4)支付链接生成:支付宝支付链接
-
5)入库(两个表)的信息准备
【2】代码实现
(1)视图类
luffyCity\luffyCity\apps\orders\views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from luffyCity.apps.orders.serializers.OrderSerializers import PaySerializer
from luffyCity.libs.ALiPaySDK import ALiPay
from luffyCity.utils.common_response import CommonResponse
from rest_framework.viewsets import GenericViewSet
from rest_framework.permissions import IsAuthenticated
# Create your views here.
# 测试封装包
class ALiPayView(APIView):
def get(self, request, *args, **kwargs):
pay = ALiPay()
order_id = "201102102102102150"
total_amount = 644491.03
order_name = '兰博基尼'
order_url = pay.create_order_url(order_id=order_id, total_amount=total_amount, order_name=order_name)
return CommonResponse(order_url=order_url)
class PayOrderView(GenericViewSet):
serializer_class = PaySerializer
authentication_classes = [JSONWebTokenAuthentication]
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
pay_ser = self.get_serializer(context={"request": request}, data=request.data)
pay_ser.is_valid(raise_exception=True)
pay_ser.save()
pay_url = pay_ser.context.get('pay_url')
return CommonResponse(msg="保存成功", pay_url=pay_url)
(2)路由
luffyCity\luffyCity\apps\orders\urls.py
from django.urls import path
from luffyCity.apps.orders.views import ALiPayView, PayOrderView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('pay', PayOrderView, 'pay')
urlpatterns = [
# path('pay/', ALiPayView.as_view()),
]
urlpatterns += router.urls
(3)序列化类
luffyCity\luffyCity\apps\orders\serializers\OrderSerializers.py
# -*-coding: Utf-8 -*-
# @File : OrderSerializers .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/8/13
import uuid
from luffyCity.libs.ALiPaySDK import ALiPay
from rest_framework import serializers
from rest_framework.exceptions import APIException
from luffyCity.apps.course.models import Course
from luffyCity.apps.orders.models import Order, OrderDetail
# 校验 -- 保存
class PaySerializer(serializers.ModelSerializer):
'''
- 1)订单总价校验
- 2)生成订单号:唯一的
- 3)获取支付用户:request.user
- 4)支付链接生成:支付宝支付链接
- 5)入库(两个表)的信息准备
'''
# 要支持单购物和群购物(购物车),前台要提交 课程主键(们)
# PrimaryKeyRelatedField:前端传入 [1,2,3] ---帮我们转成---> [id为1的课程对象,id为2的课程对象,id为3的课程对象]
courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
class Meta:
model = Order
fields = ['subject', 'total_amount', 'pay_type', 'courses']
def _check_price(self, attrs):
'''
(1)取出前端传入的总价格
(2)计算总价格,核验总价格
:return:
'''
# (1)取出前端传入的总价格
total_amount = attrs.get('total_amount')
# (2)计算总价格,核验总价格
# course 经过上面的字段序列已经转成了对象
courses = attrs.get('courses')
real_total_amount = 0
for course in courses:
real_total_amount += course.price
if not real_total_amount == total_amount:
raise APIException("两次价格不一致")
def _create_order_id(self):
return str(uuid.uuid4())
def _get_user(self):
request = self.context.get('request')
return request.user
def _get_pay_url(self, order_id, subject, total_amount, pay_type):
# pay_type 如果是多方式付款,这里需要判断一下
pay = ALiPay()
pay_url = pay.create_order_url(order_id=order_id, total_amount=float(total_amount), order_name=subject)
return pay_url
def validate(self, attrs):
# 1)订单总价校验
self._check_price(attrs)
# 2)生成订单号:唯一的
order_id = self._create_order_id()
# 3)获取支付用户:request.user
user = self._get_user()
# 4)支付链接生成:支付宝支付链接
pay_url = self._get_pay_url(order_id, attrs.get('subject'), attrs.get('total_amount'), attrs.get('pay_type'))
# 5)入库(两个表)的信息准备
attrs['user'] = user
attrs['out_trade_no'] = order_id
self.context['pay_url'] = pay_url
# 当前 attrs 中数据 {'subject', 'total_amount', 'pay_type', 'courses','user','order_id'}
return attrs
def create(self, validated_data):
# 当前 attrs 中数据 {'subject', 'total_amount', 'pay_type', 'courses','user','order_id'}
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
【三】支付宝回调
-
因为咱们 地址是 127.0.0.1
- 支付宝回调,只能回调公网地址,调不回我们这,我们收不到支付宝的回调--->
-
解决方案:
- 1 使用公网ip部署项目
- 2 使用内网穿透
https://zhuanlan.zhihu.com/p/370483324