day88:luffy:支付宝同步结果通知&接收异步支付结果&用户购买记录&我的订单
目录
1.支付宝同步结果通知
1.get请求支付宝,支付宝返回给你的参数
当用户输入用户名和密码确认支付的时候,支付宝会给vue前端回复一个url,这个url很长,后面包含了很多参数,参数如下所示:
http://www.luffycity.cn:8080/payments/result?
charset=utf8&
out_trade_no=20190929151453000001000020&
method=alipay.trade.page.pay.return&
total_amount=310.00&
sign=kebIZBI%2FpCNXmCivfJPPw21gcobulPZoSh%2BXiHR8l6cgexQi2STG4AZgr%2FEUhvc5kEMacJLvCmBaw1Wqo4WK3sPzbUaPmzq3NshUNzYK2lWTsmOauidNxlk1bK0Q%2FANBfQUkmj6TQjyB5T9QqEnS80KFsDrGrasU%2B%2Fz9W%2FjOCLrSji6TnKhRkI9pqBMdw823ABU75b7zOtXzcXKduO%2B6vsXVvluMzedss9dHs1celxPAWQV9jcKjzq%2F1bPbZcmgAGNQQecoJ%2BFSc3uTmTk24uV39PM54LIlg8aeRlkPNjvhBkJh%2FG0%2BURNDdG2593IFIF%2BUqoU%2F7ixm19dX222GCWg%3D%3D&
trade_no=2019092922001439881000120282&
auth_app_id=2016091600523592&
version=1.0&
app_id=2016091600523592&
sign_type=RSA2&
seller_id=2088102175868026&
timestamp=2019-09-29%2015%3A15%3A53
这些参数的含义:https://opendocs.alipay.com/apis/api_1/alipay.trade.page.pay
2.后端实现处理支付宝同步通知结果的视图
支付宝将参数传递给了vue前端,vue前端要这些参数发送给后端,让后端去对这些参数做一个校验。判断这个url是不是支付宝发过来的
后端校验支付宝返回给你的那些参数
payment/urls.py
from django.urls import path,re_path
from . import views urlpatterns = [
path('result/',views.AlipayResultView.as_view(),)
]
payment/view.py
class AlipayResultView(APIView):
permission_classes = [IsAuthenticated, ]
def get(self,request): # 1.创建alipay对象
alipay = AliPay(
appid=settings.ALIAPY_CONFIG['appid'],
app_notify_url=None, # 默认回调url
app_private_key_string=open(settings.ALIAPY_CONFIG['app_private_key_path']).read(),
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_string=open(settings.ALIAPY_CONFIG['alipay_public_key_path']).read(),
sign_type=settings.ALIAPY_CONFIG['sign_type'], # RSA 或者 RSA2
debug=settings.ALIAPY_CONFIG['debug'], # 默认False
)
# 2.校验支付宝响应数据
data = request.query_params.dict() # 获取那一大堆的参数 out_trade_no = data.get('out_trade_no') # 获取商户订单号
sign = data.pop('sign') # 获取签名
success = alipay.verify(data,sign) # 通过参数和签名来验证那一堆参数是不是支付宝发过来的
if not success:
logger.error('%s,支付宝响应数据校验失败' % out_trade_no)
return Response('支付宝响应数据校验失败',status=status.HTTP_400_BAD_REQUEST) # 响应结果
return Response({'msg':'ok','data':res_data})
3.vue前端发送请求验证get参数
前端发送请求 将支付宝发给vue前端的一大堆参数传递到后端,后端做完校验返回一个成功与否的结果
如果校验没有问题 就可以显示购买成功了。
Success.vue
created(){
// 把地址栏上面的支付结果,转发给后端
this.send_alipay_params(); methods:{
send_alipay_params(){
let token = localStorage.token || sessionStorage.token;
this.$axios.get(`${this.$settings.Host}/payment/result/${location.search}`,{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{ // 如果后端验证这些参数没有问题 购买成功页面需要的参数就可以传递过来了
this.pay_time = res.data.data.pay_time; // 支付时间
this.course_list = res.data.data.course_list; // 购买课程列表
this.total_real_price = res.data.data.total_real_price; // 课程总真实价格
}).catch((error)=>{
this.$message.error(error.response.data.msg);
})
},
2.用户购买记录表
当用户购买成功之后,应该生成用户记录,主要用来存支付平台的流水号,有了这个流水号就可以去支付宝查账单了。以及课程的购买时间和过期时间。
users/models.py
class UserCourse(BaseModel):
"""用户的课程购买记录"""
pay_choices = (
(1, '用户购买'),
(2, '免费活动'),
(3, '活动赠品'),
(4, '系统赠送'),
) user = models.ForeignKey(User, related_name='user_courses', on_delete=models.DO_NOTHING, verbose_name="用户")
course = models.ForeignKey(Course, related_name='course_users', on_delete=models.DO_NOTHING, verbose_name="课程")
trade_no = models.CharField(max_length=128, null=True, blank=True, verbose_name="支付平台的流水号", help_text="将来依靠流水号到支付平台查账单")
buy_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="购买方式")
pay_time = models.DateTimeField(null=True, blank=True, verbose_name="购买时间")
out_time = models.DateTimeField(null=True, blank=True, verbose_name="过期时间") # null表示永不过期 class Meta:
db_table = 'ly_user_course'
verbose_name = '课程购买记录'
verbose_name_plural = verbose_name
3.接受异步支付结果
payment/views.py
class AlipayResultView(APIView):
permission_classes = [IsAuthenticated, ]
def post(self,request): # 创建alipay对象
alipay = AliPay(
appid=settings.ALIAPY_CONFIG['appid'],
app_notify_url=None, # 默认回调url
app_private_key_string=open(settings.ALIAPY_CONFIG['app_private_key_path']).read(),
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_string=open(settings.ALIAPY_CONFIG['alipay_public_key_path']).read(),
sign_type=settings.ALIAPY_CONFIG['sign_type'], # RSA 或者 RSA2
debug=settings.ALIAPY_CONFIG['debug'], # 默认False
) # 校验支付宝响应数据
data = request.data.dict()
sign = data.pop('sign')
success = alipay.verify(data,sign)
if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"): self.change_order_status(data) return Response('success')
4.善后事宜
当一切都完成后,还有几件事情要做:
1.修改订单状态
2.扣除优惠劵 积分
3.清空购物车数据和结算页面全部数据
4.将相关信息存到用户购买记录表中
payment/views.py
class AlipayResultView(APIView):
def change_order_status(self,data): # 修改订单状态
with transaction.atomic():
out_trade_no = data.get('out_trade_no')
trade_no = data.get('trade_no') # A.修改订单状态
# 1.获取当前订单号的订单对象
order_obj = Order.objects.get(order_number=out_trade_no) # 2.将当前订单的订单状态改为1:已支付
order_obj.order_status = 1 # 3.保存订单信息
order_obj.save() # B.修改优惠券的使用状态
if order_obj.coupon > 0: # 如果用户使用了优惠劵
# 1.获取用户使用的那张优惠劵对象
user_coupon_obj = UserCoupon.objects.get(is_use=False, id=order_obj.coupon) # 2.将用户使用的那张优惠劵的状态由未使用改为已使用
user_coupon_obj.is_use = True # 3.保存用户的优惠劵的信息
user_coupon_obj.save() # C.支付成功,用户积分应该对应的扣除 # 1.查询当前订单使用了多少积分
use_credit = order_obj.credit # 2.查询到用户的总积分数减去使用的积分数得到用户的剩余积分数
self.request.user.credit -= use_credit # 3.保存用户的积分信息
self.request.user.save() # D.保存支付宝的交易流水号(购买记录表) # 1.通过订单对象反向查询到所有的订单详情对象
order_detail_objs = order_obj.order_courses.all() # 2.获取当前时间
now = datetime.datetime.now() # 购买成功 从redis中将课程的选中状态删除掉
conn = get_redis_connection('cart')
pipe = conn.pipeline()
pipe.delete('selected_cart_%s' % self.request.user.id) # 需要给购买成功页面(Success.vue返回的数据)
res_data = {
'pay_time': now,
'course_list': [],
'total_real_price': order_obj.real_price,
} for order_detail in order_detail_objs:
# 购买成功 课程学习的学生数+1
course = order_detail.course
course.students += 1
course.save() # 购买成功的课程应该显示课程列表的每个课程
res_data['course_list'].append(course.name) # 从课程详情页获取当前课程的有效期数值
expire_id = order_detail.expire
if expire_id > 0: # 如果不是永久有效 # 根据expire_id查询到课程有效期model对象
expire_obj = CourseExpire.objects.get(id=expire_id) # 查询到课程的有效期(天数)
expire_time = expire_obj.expire_time # 计算课程的过期时间
out_time = now + datetime.timedelta(days=expire_time)
else: # 如果是永久有效,就没有过期时间
out_time = None # 一切处理完毕,将相关信息存到用户购买记录表中
UserCourse.objects.create(**{
'user':self.request.user,
'course':course,
'trade_no':trade_no,
'buy_type':1,
'pay_time':now,
'out_time':out_time, })
# 购物车redis数据删除
pipe.hdel('cart_%s' % self.request.user.id, course.id)
pipe.execute() return res_data
order/serializers.py
def create:
order_obj.coupon = coupon_id
order_obj.credit = credit
order_obj.save()
5.我的订单
1.我的订单界面-初始化
Myorder.vue
<template>
<div class="user-order">
<Vheader/>
<div class="main">
<div class="banner"></div>
<div class="profile">
<div class="profile-info">
<div class="avatar"><img class="newImg" width="100%" alt="" src="../../static/img/logo@2x.png"></div>
<span class="user-name">吴某某</span>
<span class="user-job">北京市 | 程序员</span>
</div>
<ul class="my-item">
<li>我的账户</li>
<li class="active">我的订单</li>
<li>个人资料</li>
<li>账号安全</li>
</ul>
</div>
<div class="user-data">
<ul class="nav">
<li class="order-info">订单</li>
<li class="course-expire">有效期</li>
<li class="course-price">课程价格</li>
<li class="real-price">实付金额</li>
<li class="order-status">交易状态</li>
<li class="order-do">交易操作</li>
</ul>
<div class="my-order-item" v-for="(order_obj,index) in order_list" :key="index">
<div class="user-data-header">
<span class="order-time">xxxx</span>
<span class="order-num">订单号:
<span class="my-older-number">xxxx</span>
</span>
</div>
<ul class="nav user-data-list" v-for="(course_obj,course_index) in order_obj.order_detail_data">
<li class="order-info">
<img :src="course_obj.course_img" alt="">
<div class="order-info-title">
<p class="course-title">xxxx</p>
<p class="price-service">xxxx</p>
</div>
</li>
<li class="course-expire">xxxx</li>
<li class="course-price">xxxx</li>
<li class="real-price">xxxx</li>
<li class="order-status">xxxx</li>
<li class="order-do">
<span class="btn btn2" v-if="order_obj.get_order_status_display==='已支付'">去学习</span>
<span class="btn btn2" v-else-if="order_obj.get_order_status_display==='未支付'" @click="go_pay(order_obj.order_number)">去付款</span>
<span class="btn btn2" v-else-if="order_obj.get_order_status_display==='超时取消'">超时取消</span>
<span class="btn btn2" v-else>已取消</span>
</li>
</ul>
</div>
</div>
</div>
<Footer/>
</div>
</template> <script>
import Vheader from "./common/Vheader"
import Footer from "./common/Footer"
export default{
name:"Myorder",
data(){
return { };
},
created(){ },
methods:{ },
components:{
Vheader,
Footer,
}
}
</script> <style scoped>
.main .banner{
width: 100%;
height: 324px;
background: url(../../static/img/my_bkging.0648ebe.png) no-repeat;
background-size: cover;
z-index: 1;
}
.profile{
width: 1200px;
margin: 0 auto;
}
.profile-info{
text-align: center;
margin-top: -80px;
}
.avatar{
width: 120px;
height: 120px;
border-radius: 60px;
overflow: hidden;
margin: 0 auto;
}
.user-name{
display: block;
font-size: 24px;
color: #4a4a4a;
margin-top: 14px;
}
.user-job{
display: block;
font-size: 11px;
color: #9b9b9b;
}
.my-item{
list-style: none;
line-height: 1.42857143;
color: #333;
width: 474px;
height: 31px;
display: -ms-flexbox;
display: flex;
cursor: pointer;
margin: 41px auto 0;
-ms-flex-pack: justify;
justify-content: space-between;
}
.my-item .active{
border-bottom: 1px solid #000;
}
.user-data{
width: 1200px;
height: auto;
margin: 0 auto;
padding-top: 30px;
border-top: 1px solid #e8e8e8;
margin-bottom: 63px;
}
.nav{
width: 100%;
height: 60px;
background: #e9e9e9;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
}
.nav li{
margin-left: 20px;
margin-right: 28px;
height: 60px;
line-height: 60px;
list-style: none;
font-size: 13px;
color: #333;
border-bottom: 1px solid #e9e9e9;
width: 160px;
}
.nav .order-info{ width: 325px; }
.nav .course-expire{ width: 60px; }
.nav .course-price{ width: 130px; }
.user-data-header{
display: flex;
height: 44px;
color: #4a4a4a;
font-size: 14px;
background: #f3f3f3;
-ms-flex-align: center;
align-items: center;
}
.order-time{
font-size: 12px;
display: inline-block;
margin-left: 20px;
}
.order-num{
font-size: 12px;
display: inline-block;
margin-left: 29px;
}
.user-data-list{
height: 100%;
display: flex;
}
.user-data-list{
background: none;
}
.user-data-list li{
height: 60px;
line-height: 60px;
}
.user-data-list .order-info{
display: flex;
align-items: center;
margin-right: 28px;
}
.user-data-list .order-info img{
max-width: 100px;
max-height: 75px;
margin-right: 22px;
}
.course-title{
width: 203px;
font-size: 13px;
color: #333;
line-height: 5px;
margin-top: -10px;
}
.order-info-title .price-service{
line-height: 18px;
}
.price-service{
font-size: 12px;
color: #fa6240;
padding: 0 5px;
border: 1px solid #fa6240;
border-radius: 4px;
margin-top: 4px;
position: absolute;
}
.order-info-title{
margin-top: -10px;
}
.user-data-list .course-expire{
font-size: 12px;
color: #ff5502;
width: 60px;
text-align: center;
}
.btn {
width: 100px;
height: 32px;
font-size: 14px;
color: #fff;
background: #ffc210;
border-radius: 4px;
border: none;
outline: none;
transition: all .25s ease;
display: inline-block;
line-height: 32px;
text-align: center;
cursor: pointer;
}
</style>
我的订单页面-初始化
index.js
{
path: '/myorder/',
component: Myorder
},
2.我的订单页面-后端接口
users/urls.py
urlpatterns = [
path(r'myorder/', views.MyOrderView.as_view()),
]
users/views.py
class MyOrderView(ListAPIView):
permission_classes = [IsAuthenticated, ]
serializer_class = MyOrderModelSerializer def get_queryset(self):
return Order.objects.filter(user=self.request.user)
user/serializers.py
class MyOrderModelSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ['id', 'order_number' ,'pay_time', 'get_order_status_display', 'order_detail_data']
order/models.py
在我的订单页面中,需要展示一些数据
class Order(BaseModel):
def order_detail_data(self):
# 获取所有课程详情对象
order_detail_objs = self.order_courses.all()
data_list = [] for order_detail in order_detail_objs:
expire_id = order_detail.expire # 根据有效期来决定expire_text返回什么
if expire_id > 0:
expire_obj = CourseExpire.objects.get(id=expire_id)
expire_text = expire_obj.expire_text
else:
expire_text = '永久有效' # 每个课程应该包含的字段
order_dict = {
'course_img':contains.SERVER_ADDR + order_detail.course.course_img.url,
'course_name':order_detail.course.name,
'expire_text':expire_text,
'price':order_detail.price,
'real_price': self.real_price,
'discount_name':order_detail.discount_name, }
# 将每个课程的详情信息添加到一个列表里返回给前端
data_list.append(order_dict) return data_list
3.我的订单页面-前端
1.获取后端的订单数据
Myorder.vue
// js
get_order_data(){
// 检查当前访问者是否登录了!
let token = localStorage.token || sessionStorage.token;
this.$axios.get(`${this.$settings.Host}/users/myorder/`,{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.order_list = res.data;
}).catch((error)=>{ })
<!-- html -->
<div class="my-order-item" v-for="(order_obj,index) in order_list" :key="index">
<div class="user-data-header">
<span class="order-time">{{order_obj.pay_time.replace('T', ' ')}}</span>
<span class="order-num">订单号:
<span class="my-older-number">{{order_obj.order_number}}</span>
</span>
</div>
<ul class="nav user-data-list" v-for="(course_obj,course_index) in order_obj.order_detail_data">
<li class="order-info">
<img :src="course_obj.course_img" alt="">
<div class="order-info-title">
<p class="course-title">{{course_obj.course_name}}</p>
<p class="price-service">{{course_obj.discount_name}}</p>
</div>
</li>
<li class="course-expire">{{course_obj.expire_text}}</li>
<li class="course-price">{{course_obj.price}}</li>
<li class="real-price">{{course_obj.real_price}}</li>
<li class="order-status">{{order_obj.get_order_status_display}}</li>
<li class="order-do">
<span class="btn btn2" v-if="order_obj.get_order_status_display==='已支付'">去学习</span>
<span class="btn btn2" v-else-if="order_obj.get_order_status_display==='未支付'" @click="go_pay(order_obj.order_number)">去付款</span>
<span class="btn btn2" v-else-if="order_obj.get_order_status_display==='超时取消'">超时取消</span>
<span class="btn btn2" v-else>已取消</span>
</li>
</ul>
</div>
2.我的订单页面点击去付款
Myorder.vue
<!-- html -->
<span class="btn btn2" v-else-if="order_obj.get_order_status_display==='未支付'" @click="go_pay(order_obj.order_number)">去付款</span>
// js
go_pay(order_number){
let token = localStorage.token || sessionStorage.token;
this.$axios.get(`${this.$settings.Host}/payment/alipay/?order_number=${order_number}`,{
headers:{
'Authorization':'jwt ' + token
} }).then((res)=>{
location.href = res.data.url; }).catch((error)=>{
this.$message.error(error.response.data.msg);
})
day88:luffy:支付宝同步结果通知&接收异步支付结果&用户购买记录&我的订单的更多相关文章
- 通知url必须为直接可访问的url,不能携带参数 异步接收微信支付结果通知的回调地址 不能携带参数。 回调地址后是否可以加自定义参数 同步回调地址 异步回调地址 return_url和notify_url的区别
[微信支付]微信小程序支付开发者文档 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7 通知url必须为直接可访问的 ...
- day114:MoFang:基于支付宝沙箱测试环境完成创建充值订单接口&服务端处理支付结果的同步通知和异步通知
目录 1.基于支付宝提供的沙箱测试环境开发支付接口 1.后端提供创建充值订单接口 2.前端调用AlipayPlus发起支付 3.注意:自定义APPLoader完成接下来的开发 4.下载支付宝沙箱钱包A ...
- 同步I/O、异步I/O与阻塞I/O、非阻塞I/O的区别
一.I/O I/O (Input/Output,输入/输出)即数据的读取(接收)或写入(发送)操作. 通常用户进程中的一个完整I/O分为两阶段:用户进程空间<-->内核空间.内核空间< ...
- 《Windows核心编程系列》十谈谈同步设备IO与异步设备IO之异步IO
同步设备IO与异步设备IO之异步IO介绍 设备IO与cpu速度甚至是内存访问相比较都是比较慢的,而且更不可预测.虽然如此,通过使用异步设备IO我们仍然能够创造出更高效的程序. 同步IO时,发出IO请求 ...
- Linux设备驱动中的异步通知与异步I/O
异步通知概念: 异步通知的意识是,一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上的“中断”概念,比较准确的称谓是“信号驱动的异步IO”,信号是在软件层次 ...
- .NET Core 实践二:事件通知和异步处理
首先让我们来先看一个例子: 这是一个简单的用户下单购买商品的业务模型,输入端是用户,相关物料有订单和货物,相关的内部服务有业务(订单).财务(支付).仓储(备货)和物流(运输). 从图中我们可以看到, ...
- 转:IO模型-- 同步和阻塞,异步和非阻塞的区别
源地址 http://hi.baidu.com/deep_pro/item/db0c581af1c1f17e7b5f2534 这些词之间的区别难倒了很多人,还有什么同步阻塞, 同步非阻塞, 异步阻塞, ...
- arm驱动linux异步通知与异步IO【转】
转自:http://blog.csdn.net/chinazhangzhong123/article/details/51638793 <[ arm驱动] linux异步通知与 异步IO> ...
- Windows核心编程:第10章 同步设备IO与异步设备IO
Github https://github.com/gongluck/Windows-Core-Program.git //第10章 同步设备IO与异步设备IO.cpp: 定义应用程序的入口点. // ...
随机推荐
- Java之格林威治时间格式转换成北京时间格式
Java之格林威治时间格式转换成北京时间格式 package com.mtons.mblog; import java.text.ParseException; import java.text.Si ...
- Docker笔记6:Docker 常见命令及镜像管理
目 录 一.Docker 常用命令 docker version 命令 docker info 命令 二.Docker 镜像管理 搜索镜像:docker search 镜像名 获取镜像:docker ...
- 多测师全方位面试题腾讯 _自动化面试题_高级讲师肖sir
作答注意:候选人可以两题都做,也可以两题任选一题做即可. 笔试题一:1.查询 https://www.newsmth.net/nForum/#!board/PieLove2.获取发贴时间是2020年8 ...
- html中object标签
首先将这个强大web页面打印例子(pintTest.html)贴上来. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional ...
- mybatis-plus自动填充
1,给字段添加注解 @TableField(value = "create_time", fill = FieldFill.INSERT) 2,添加填充处理器,需要实现接口Meta ...
- bash xshell 特性
1.tab键补全 2.命令行常用快捷键: ctrl键+ c #取消当前操作 ctrl键+ d #退出当前用户登录 ctrl键+ a #光标移动到光标所在行的行首 ctrl键+ e ...
- 赛门铁克和DigiCert证书有什么区别?
在众多国人眼里,赛门铁克Symantec名气更胜于DigiCert证书.但是,我们知道2017年赛门铁克因一系列原因被DigiCert收购,品牌名称也被更新为DigiCert Secure Site. ...
- 如何計算n個圓的聯集面積
如何計算n個圓的聯集面積 前言 一般人第一次遇到這個問題,可能會想要想辦法用排容原理,找圓之間交疊的凸包之類的.... 然而我只要舉一個例子,你就會發現我們就算把凸包找出來了,我們也非常難知道找到的凸 ...
- 手撸ORM浅谈ORM框架之Delete篇
快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...
- 小白:String函数总结
string.h函数: 1.strlen 数出字符串存在多少字符: 2.strcmp 比较两个字符串,若相等返回0不相等返回1 3.strcpy(char *restrict dst,const ch ...