支付和订单
Posted pankypan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了支付和订单相关的知识,希望对你有一定的参考价值。
支付和订单
为了方便开发,和以后项目的维护,我们再次创建子应用orders来完成接下来的订单和订单支付功能。
cd luffy/apps
python ../../manage.py startapp orders
注册子应用,settings/dev.py,代码:
INSTALLED_APPS = [
# 子应用
。。。
'orders',
]
订单模型
from django.db import models
# Create your models here.
from luffy.utils.models import BaseModel
from users.models import User
from courses.models import Course
class Order(BaseModel):
"""订单记录"""
status_choices = (
(0, '未支付'),
(1, '已支付'),
(2, '已取消'),
(3, '超时取消'),
)
pay_choices = (
(0, '支付宝'),
(1, '微信支付')
)
order_title = models.CharField(max_length=150,verbose_name="订单标题")
total_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="订单总价", default=0)
real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="实付金额", default=0)
order_number = models.CharField(max_length=64,verbose_name="订单号")
order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
pay_type = models.SmallIntegerField(choices=pay_choices, default=0, verbose_name="支付方式")
use_coupon = models.BooleanField(default=False,verbose_name="是否使用优惠券")
coupon = models.IntegerField(null=True, verbose_name="用户优惠券ID")
order_desc = models.TextField(max_length=500,null=True,blank=True, verbose_name="订单描述")
pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
user = models.ForeignKey(User, related_name='user_orders', on_delete=models.DO_NOTHING,verbose_name="下单用户")
class Meta:
db_table="ly_order"
verbose_name= "订单记录"
verbose_name_plural= "订单记录"
def __str__(self):
return "%s,总价: %s,实付: %s" % (self.order_title, self.total_price, self.real_price)
from courses.models import CourseTime
class OrderDetail(BaseModel):
"""订单详情"""
order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, verbose_name="订单")
course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, verbose_name="课程")
expire = models.IntegerField(default='-1', 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="课程实价")
discount_name = models.CharField(max_length=120,default="",verbose_name="优惠活动类型")
class Meta:
db_table="ly_order_detail"
verbose_name= "订单详情"
verbose_name_plural= "订单详情"
makemigrations && migrate
python manage.py makemigrations;
python manage.py migrate;
把当前子应用注册到xadmin中
在当前子应用下创建adminx.py,代码:
import xadmin
from .models import Order
class OrderModelAdmin(object):
"""订单模型管理类"""
pass
xadmin.site.register(Order, OrderModelAdmin)
from .models import OrderDetail
class OrderDetailModelAdmin(object):
"""订单详情模型管理类"""
pass
xadmin.site.register(OrderDetail, OrderDetailModelAdmin)
后端实现生成订单的api接口
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from django_redis import get_redis_connection
from rest_framework.response import Response
from .models import Order,OrderDetail
from datetime import datetime
from courses.models import Course, CourseTime
import random
from django.db import transaction
from rest_framework import status
import logging
log = logging.getLogger("django")
class OrderAPIView(APIView):
permission_classes = [IsAuthenticated]
def post(self,request):
"""生成订单"""
# 获取用户ID
# user_id = 1
user_id = request.user.id
# 订单号,必须保证唯一
order_number = datetime.now().strftime("%Y%m%d%H%M%S") + "%08d" % user_id + "%04d" % random.randint(0,9999)
with transaction.atomic():
# 数据库事务的回滚标记
save_id = transaction.savepoint()
# 生成空的订单
try:
order = Order.objects.create(
order_title="路飞学城课程购买",
total_price=0,
real_price=0,
order_number= order_number,
user_id=user_id,
)
# 到redis获取购物车信息
redis = get_redis_connection("cart")
# 勾选状态
course_selects_set = redis.smembers("cart_selected_%s" % user_id )
print( course_selects_set )
# 购物车中商品课程列表
cart_course_list = redis.hgetall("cart_%s" % user_id )
print( cart_course_list )
# 通过购物车信息到数据中提取相关数据
# 计算订单总价
total_price = 0
# 开启redis的管道操作[事务操作]
pipeline = redis.pipeline()
pipeline.multi()
for course_id_byte,expire_byte in cart_course_list.items():
if course_id_byte in course_selects_set:
expire = expire_byte.decode()
course_id = course_id_byte.decode()
course = Course.objects.get(pk=course_id)
if expire == '-1':
"""永久有效"""
course_price = course.get_course_price()
else:
"""有购买周期"""
coursetime = CourseTime.objects.get(course=course_id,timer=expire)
course_price = coursetime.course.get_course_price(coursetime.price)
total_price += course_price
# 生成订单详情
OrderDetail.objects.create(
order_id=order.id,
course_id=course_id,
expire=expire,
price=course.price if expire=='-1' else coursetime.price,
real_price=course_price,
discount_name=course.get_course_discount_type()
)
# 从购物车中移除已经加入订单的商品
pipeline.srem("cart_selected_%s" % user_id, course_id )
pipeline.hdel("cart_%s" % user_id, course_id )
# 提交redis的事务操作
pipeline.execute()
# 补充订单的总价格
order.total_price=total_price
order.real_price=total_price
order.save()
except Exception:
# 记录错误日志
log.error( "%s" % Exception )
# 回滚事务
transaction.savepoint_rollback(save_id)
# 响应结果
return Response("message":"系统异常!",status=status.HTTP_507_INSUFFICIENT_STORAGE)
# 响应结果
return Response("message":"成功生成订单!","order":order.order_number)
上面我们使用了redis的事务操作保证数据的一致性。但是mysql里面我们也是在进行多表操作,所以也是需要使用事务来保证数据的一致性的。
事务有四大特性:
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)[事务隔离级别->幻读,脏读]
持久性(Durability)
django框架本身就提供了2种事务操作的用法。
django的事务操作方法主要通过 django.db.transation模块完成的。
启用事务用法1:
from django.db import transaction
from rest_framework.views import APIView
class OrderAPIView(APIView):
@transaction.atomic # 开启事务,当方法执行完成以后,自动提交事务
def post(self,request):
....
启用事务用法2:
from django.db import transaction
from rest_framework.views import APIView
class OrderAPIView(APIView):
def post(self,request):
....
with transation.atomic(): # 开启事务,当with语句执行完成以后,自动提交事务
# 数据库操作
在使用事务过程中, 有时候会出现异常,当出现异常的时候,我们需要让程序停止下来,同时需要回滚事务。
from django.db import transaction
from rest_framework.views import APIView
class OrderAPIView(APIView):
def post(self,request):
....
with transation.atomic():
# 设置事务回滚的标记点
sid = transation.savepoint()
....
try:
....
except:
transation.savepoint_rallback(sid)
使用django提供的mysql事务操作保证下单过程中的数据一致性
视图代码:
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from django_redis import get_redis_connection
from rest_framework.response import Response
from .models import Order,OrderDetail
from datetime import datetime
from courses.models import Course, CourseTime
import random
from django.db import transaction
from rest_framework import status
import logging
log = logging.getLogger("django")
class OrderAPIView(APIView):
permission_classes = [IsAuthenticated]
def post(self,request):
"""生成订单"""
# 获取用户ID
# user_id = 1
user_id = request.user.id
# 订单号,必须保证唯一
order_number = datetime.now().strftime("%Y%m%d%H%M%S") + "%08d" % user_id + "%04d" % random.randint(0,9999)
with transaction.atomic():
# 数据库事务的回滚标记
save_id = transaction.savepoint()
# 生成空的订单
try:
order = Order.objects.create(
order_title="路飞学城课程购买",
total_price=0,
real_price=0,
order_number= order_number,
user_id=user_id,
)
# 到redis获取购物车信息
redis = get_redis_connection("cart")
# 勾选状态
course_selects_set = redis.smembers("cart_selected_%s" % user_id )
print( course_selects_set )
# 购物车中商品课程列表
cart_course_list = redis.hgetall("cart_%s" % user_id )
print( cart_course_list )
# 通过购物车信息到数据中提取相关数据
# 计算订单总价
total_price = 0
# 开启redis的管道操作[事务操作]
pipeline = redis.pipeline()
pipeline.multi()
for course_id_byte,expire_byte in cart_course_list.items():
if course_id_byte in course_selects_set:
expire = expire_byte.decode()
course_id = course_id_byte.decode()
course = Course.objects.get(pk=course_id)
if expire == '-1':
"""永久有效"""
course_price = course.get_course_price()
else:
"""有购买周期"""
coursetime = CourseTime.objects.get(course=course_id,timer=expire)
course_price = coursetime.course.get_course_price(coursetime.price)
total_price += course_price
# 生成订单详情
OrderDetail.objects.create(
order_id=order.id,
course_id=course_id,
expire=expire,
price=course.price if expire=='-1' else coursetime.price,
real_price=course_price,
discount_name=course.get_course_discount_type()
)
# 从购物车中移除已经加入订单的商品
pipeline.srem("cart_selected_%s" % user_id, course_id )
pipeline.hdel("cart_%s" % user_id, course_id )
# 提交redis的事务操作
pipeline.execute()
# 补充订单的总价格
order.total_price=total_price
order.real_price=total_price
order.save()
except Exception:
# 记录错误日志
log.error( "%s" % Exception )
# 回滚事务
transaction.savepoint_rollback(save_id)
# 响应结果
return Response("message":"系统异常!",status=status.HTTP_507_INSUFFICIENT_STORAGE)
# 响应结果
return Response("message":"成功生成订单!","order":order.order_number)
change the utils/models.py
from django.db import models
class BaseModel(models.Model):
"""公共字段模型"""
orders = models.IntegerField(verbose_name='显示顺序', null=True, blank=True)
is_show = models.BooleanField(verbose_name="是否上架", default=False)
is_delete = models.BooleanField(verbose_name="逻辑删除", default=False)
create_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间")
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
# 设置当前模型在数据迁移的时候不要为它创建表
abstract = True
the main urls.py
from django.contrib import admin
from django.urls import path, re_path, include
import xadmin
from xadmin.plugins import xversion
from django.views.static import serve
from django.conf import settings
xadmin.autodiscover()
xversion.register_models() # version模块自动注册需要版本控制的 Model
urlpatterns = [
......
path('orders/', include("orders.urls")),
]
orders/urls.py
from django.urls import path, re_path
from . import views
urlpatterns = [
path("", views.OrderAPIView.as_view()),
]
前端请求生成订单
<template>
。。。
<span class="go-pay" @click="gotopay">去结算</span>
。。。
</template>
<script>
import Header from "./common/Header"
import Footer from "./common/Footer"
export default
name: "Cart",
。。。。
methods:
。。。。
gotopay()
// 提交结算,生成订单
this.$axios.post(this.$settings.Host+"/orders/",,
headers:
// 注意:jwt后面必须有且只有一个空格!!!!
"Authorization":"jwt " + this.token
).then(response=>
let _this = this;
this.$alert(response.data.message,"提示",
callback()
_this.$router.push(`orders/$response.data.order`);
)
).catch(error=>
console.log(error.response)
)
,
components:Header,Footer
</script>
create a Order.vue
<template>
</template>
<script>
export default
name: "Order"
</script>
<style scoped>
</style>
router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '../components/Home'
import Login from "../components/Login";
import Register from "../components/Register";
import Course from "../components/Course";
import Detail from "../components/Detail";
import Player from "../components/Player";
import Cart from "../components/Cart";
import Order from "../components/Order";
Vue.use(Router);
export default new Router(
mode: 'history',
routes: [
...
name: "Order",
path: "/orders/:order", // 地址栏的命名绑定参数, 在组件中可以通过 this.$router.param.order可以获取数据
component: Order,
]
)
显示结算页面
Order.vue
<template>
<div class="cart">
<Header/>
<div class="cart-info">
<h3 class="cart-top">购物车结算 <span>共1门课程</span></h3>
<div class="cart-title">
<el-row>
<el-col :span="2"> </el-col>
<el-col :span="10">课程</el-col>
<el-col :span="8">有效期</el-col>
<el-col :span="4">价格</el-col>
</el-row>
</div>
<div class="cart-item" v-for="item in course_list">
<el-row>
<el-col :span="2" class="checkbox"> </el-col>
<el-col :span="10" class="course-info">
<img :src="item.course.course_http_img" alt="">
<span>item.course.name</span>
</el-col>
<el-col :span="8"><span>永久有效</span></el-col>
<el-col :span="4" class="course-price">¥item.unit_price</el-col>
</el-row>
</div>
<div class="calc">
<el-row class="pay-row">
<el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col>
<el-col :span="8">
<span class="alipay"><img src="../../static/img/alipay2.png" alt=""></span>
<span class="alipay wechat"><img src="../../static/img/wechat.png" alt=""></span>
</el-col>
<el-col :span="8" class="count">实付款: <span>¥total</span></el-col>
<el-col :span="4" class="cart-pay"><span @click="payhander">支付宝支付</span></el-col>
</el-row>
</div>
</div>
<Footer/>
</div>
</template>
<script>
import Header from "./common/Header"
import Footer from "./common/Footer"
export default
name:"Order",
data()
return
token: localStorage.token || sessionStorage.token,
id: localStorage.id || sessionStorage.id,
order_id:sessionStorage.order_id || null,
course_list:[],
total:0,
,
components:
Header,
Footer,
,
created()
,
methods:
payhander()
</script>
<style scoped>
.cart
margin-top: 80px;
.cart-info
overflow: hidden;
width: 1200px;
margin: auto;
.cart-top
font-size: 18px;
color: #666;
margin: 25px 0;
font-weight: normal;
.cart-top span
font-size: 12px;
color: #d0d0d0;
display: inline-block;
.cart-title
background: #F7F7F7;
height: 70px;
.calc
margin-top: 25px;
margin-bottom: 40px;
.calc .count
text-align: right;
margin-right: 10px;
vertical-align: middle;
.calc .count span
font-size: 36px;
color: #333;
.calc .cart-pay
margin-top: 5px;
width: 110px;
height: 38px;
outline: none;
border: none;
color: #fff;
line-height: 38px;
background: #ffc210;
border-radius: 4px;
font-size: 16px;
text-align: center;
cursor: pointer;
.cart-item
height: 120px;
line-height: 120px;
.course-info img
width: 175px;
height: 115px;
margin-right: 35px;
vertical-align: middle;
.alipay
display: inline-block;
height: 48px;
.alipay img
height: 100%;
width:auto;
.pay-text
display: block;
text-align: right;
height: 100%;
line-height: 100%;
vertical-align: middle;
margin-top: 20px;
</style>
后端提供订单数据的api
orders/serializers.py,序列化器,代码:
from rest_framework import serializers
from .models import Order,OrderDetail
class OrderCourseSerializer(serializers.ModelSerializer):
class Meta:
model = OrderDetail
fields = ("course_name","expire_text","price","real_price","discount_name","course_img")
class OrderDetailModelSerializer(serializers.ModelSerializer):
order_courses = OrderCourseSerializer(many=True)
class Meta:
model = Order
fields = ("id", "total_price", "real_price","pay_type","use_coupon","coupon","order_courses")
模型中新增返回课程图片的字段,代码:
from courses.models import CourseTime
class OrderDetail(BaseModel):
"""订单详情"""
order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, verbose_name="订单")
course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, verbose_name="课程")
expire = models.IntegerField(default='-1', 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="课程实价")
discount_name = models.CharField(max_length=120,default="",verbose_name="优惠活动类型")
class Meta:
db_table="ly_order_detail"
verbose_name= "订单详情"
verbose_name_plural= "订单详情"
def course_img(self):
# 返回图片的url地址
return self.course.course_img.url
def expire_text(self):
if self.expire == -1:
return "永久有效"
else:
print( self.expire )
coursetime = CourseTime.objects.get(timer=self.expire,course=self.course)
return coursetime.title
def course_name(self):
return self.course.name
视图代码:
from .serializers import OrderDetailModelSerializer
class OrderDetailAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self,request,pk):
try:
order = Order.objects.get(order_number=pk)
except Order.DoesNotExist:
return Response("message":"订单信息有误!",status=status.HTTP_400_BAD_REQUEST)
serializer = OrderDetailModelSerializer(instance=order)
return Response(serializer.data, status=status.HTTP_200_OK)
路由代码:
re_path(r"(?P<pk>\d+)/",views.OrderDetailAPIView.as_view()),
前端请求后端的订单信息
<template>
<div class="cart">
<Header/>
<div class="cart-info">
<h3 class="cart-top">购物车结算 <span>共1门课程</span></h3>
<div class="cart-title">
<el-row>
<el-col :span="2"> </el-col>
<el-col :span="10">课程</el-col>
<el-col :span="8">有效期</el-col>
<el-col :span="4">价格</el-col>
</el-row>
</div>
<div class="cart-item" v-for="course in order_info.order_courses">
<el-row>
<el-col :span="2" class="checkbox"> </el-col>
<el-col :span="10" class="course-info">
<img style="float: left;" :src="$settings.Host+course.course_img" alt="">
<span class="course_name">
<span>course.course_name</span><br>
<span class="discount_name">course.discount_name</span>
</span>
</el-col>
<el-col :span="8" class="lh"><span>course.expire_text</span></el-col>
<el-col :span="4">
<div class="course-price">
<p class="real_price">¥course.real_price</p>
<span class="original_price">原价: ¥course.price</span>
</div>
</el-col>
</el-row>
</div>
<div>
<div class="coupon">
<div id="accordion">
<div class="coupon-box">
<div class="coupon-title">
<span class="select-coupon">使用优惠劵:</span>
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne" style="width: 20px; height: 20px" class="collapsed" aria-expanded="false">
<img class="sign" src="../../static/img/12.png" width="20" height="20" alt=""></a>
<span class="coupon-num">有0张可用</span>
</div>
<p class="sum-price-wrap" style="margin-right: 45px">商品总金额:<span class="sum-price">order_info.total_price元</span></p>
</div>
<div style="text-align: left; height: 0px;" id="collapseOne" class="panel-collapse out collapse" aria-expanded="false">
<ul class="coupon-list" style="display: none;">
</ul>
<div style="text-align: center; width: 100%; padding: 50px 0px; align-items: center; justify-content: center; border-bottom: 1px solid rgb(232, 232, 232);">
<span style="font-size: 16px; color: #9b9b9b">暂无可用优惠券</span>
</div>
</div>
</div>
<div style="height: 30px; margin-top: 40px; display: flex; align-items: center; justify-content: flex-end">
<input type="checkbox" class="ok" id="color-input-red">
<label for="color-input-red"><img src=""alt=""></label>
<p class="discount-num" style="color:#9B9B9B">使用我的贝里</p>
<p class="discount-num" style="margin-right: 45px">
<span style="display: none;">可用0个已抵扣 ¥0</span>
</p>
</div>
<p class="sun-coupon-num" style="margin-right: 45px;margin-bottom:43px">优惠券抵扣:<span>0元</span></p>
</div>
</div>
<div class="calc">
<el-row class="pay-row">
<el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col>
<el-col :span="8">
<span class="alipay"><img src="../../static/img/alipay2.png" alt=""></span>
<span class="alipay wechat"><img src="../../static/img/wechat.png" alt=""></span>
</el-col>
<el-col :span="8" class="count">实付款: <span>¥order_info.total_price</span></el-col>
<el-col :span="4" class="cart-pay"><span @click="payhander">支付宝支付</span></el-col>
</el-row>
</div>
</div>
<Footer/>
</div>
</template>
<script>
import Header from "./common/Header"
import Footer from "./common/Footer"
export default
name:"Order",
data()
return
order_info:,
,
components:
Header,
Footer,
,
created()
// 判断用户是否已经登录
let token = sessionStorage.token || localStorage.token;
let _this = this;
if(!token)
this.$alert("对不起,您尚未登录!请登录!","警告",
callback()
_this.$router.push("/login");
)
// 获取地址栏上面的订单号
let order_number = this.$route.params.order;
// 发送请求获取数据
this.$axios.get(this.$settings.Host+`/orders/$order_number/`,
headers:
// 注意下方的空格!!!
"Authorization":"jwt " + token,
,
).then(response=>
this.order_info = response.data;
).catch(error=>
console.log(error.response);
)
,
methods:
payhander()
</script>
<style scoped>
.cart
margin-top: 80px;
.cart-info
overflow: hidden;
width: 1200px;
margin: auto;
.cart-top
font-size: 18px;
color: #666;
margin: 25px 0;
font-weight: normal;
.cart-top span
font-size: 12px;
color: #d0d0d0;
display: inline-block;
.cart-title
background: #F7F7F7;
height: 70px;
.calc
margin-top: 25px;
margin-bottom: 40px;
.calc .count
text-align: right;
margin-right: 10px;
vertical-align: middle;
.calc .count span
font-size: 36px;
color: #333;
.calc .cart-pay
margin-top: 5px;
width: 110px;
height: 38px;
outline: none;
border: none;
color: #fff;
line-height: 38px;
background: #ffc210;
border-radius: 4px;
font-size: 16px;
text-align: center;
cursor: pointer;
.cart-item
height: 120px;
/*line-height: 120px;*/
.cart-item .lh
line-height: 120px;
.course-info img
width: 175px;
height: 115px;
margin-right: 35px;
vertical-align: middle;
.course-price
margin-top: 40px;
.alipay
display: inline-block;
height: 48px;
.alipay img
height: 100%;
width:auto;
.pay-text
display: block;
text-align: right;
height: 100%;
line-height: 100%;
vertical-align: middle;
margin-top: 20px;
.real_price
color: #333;
margin-bottom: 10px;
.original_price
color: #9b9b9b;
letter-spacing: .36px;
text-decoration: line-through;
.coupon
margin-top: 30px;
.coupon-box
text-align: left;
display: flex;
padding-bottom: 22px;
padding-left:30px;
border-bottom: 1px solid #e8e8e8;
.coupon-title
display: flex;
.sum-price-wrap
display: inline-block;
margin-left: auto;
font-size: 16px;
color: #4a4a4a;
.discount_name
color: #ffc210;
margin-top: 24px;
font-size: 14px;
letter-spacing: .32px;
.course_name
margin-top: 40px;
display: block;
</style>
以上是关于支付和订单的主要内容,如果未能解决你的问题,请参考以下文章