一、积分抵扣
1.修改用户模型
user/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
from lyapi.utils.models import BaseModel
class User(AbstractUser):
phone = models.CharField(max_length=16,null=True,blank=True)
wechat = models.CharField(max_length=16,null=True,blank=True)
credit = models.IntegerField(default=0, blank=True, verbose_name="贝里")
class Meta:
db_table = 'ly_user'
verbose_name = '用户表'
verbose_name_plural = verbose_name
class Credit(BaseModel):
"""积分流水"""
OPERA_OPION = (
(1, "赚取"),
(2, "消费"),
)
user = models.ForeignKey("User", related_name="user_credit", on_delete=models.CASCADE, verbose_name="用户")
opera = models.SmallIntegerField(choices=OPERA_OPION,verbose_name="操作类型")
number = models.SmallIntegerField(default=0, verbose_name="积分数值")
class Meta:
db_table = 'ly_credit'
verbose_name = '积分流水'
verbose_name_plural = verbose_name
def __str__(self):
return "%s %s %s 贝壳" % ( self.user.username, self.OPERA_OPION[self.opera][1], self.number )
from course.models import Course
class UserCourse(BaseModel):
"""用户的课程购买记录"""
pay_choices = (
(1, '用户购买'),
(2, '免费活动'),
(3, '活动赠品'),
(4, '系统赠送'),
)
# 1 1
# 1 2
# 1 3
# 2 1
# 2 2
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
数据迁移
python manage.py makemigrations
python manage.py migrate
在xadmin运营站点中,给当前测试用户设置积分
2.设置积分比例
在配置文件中,设置积分和真实货币的换算比例
settings/contains.py增加配置:
# 积分兑换比例
CREDIT_MONEY = 10 # 10个积分顶一块钱
3.增加返回积分和换算比例的字段
在用户登录的代码中,增加返回积分和换算比例的字段
服务端中users/utils.py,代码;
...
from lyapi.settings import contains
def jwt_response_payload_handler(token, user=None, request=None):
# print('>>>>>',user,type(user))
return {
'token': token,
'username': user.username,
'id':user.id,
'credit':user.credit,
'credit_to_money':contains.CREDIT_MONEY,
}
...
在客户端中,保存积分到本地存储中.
Login.vue,在登录成功以后的代码中,
...
loginHandle(){
// 登录,用户名、密码、验证码数据
var captcha1 = new TencentCaptcha('2041284967', (res) =>{ //生成图片验证码的
if (res.ret === 0){
console.log(res.randstr);
console.log(res.ticket);
this.$axios.post(`${this.$settings.Host}/users/login/`,{
username:this.username,
password:this.password,
ticket:res.ticket,
randstr:res.randstr,
}).then((res)=>{
console.log(res);
// console.log(this.remember);
if (this.remember){
localStorage.token = res.data.token;
localStorage.username = res.data.username;
localStorage.id = res.data.id;
localStorage.credit = res.data.credit;
localStorage.credit_to_money = res.data.credit_to_money;
sessionStorage.removeItem('token');
sessionStorage.removeItem('username');
sessionStorage.removeItem('id');
sessionStorage.removeItem('credit');
sessionStorage.removeItem('credit_to_money');
}else {
sessionStorage.token = res.data.token;
sessionStorage.username = res.data.username;
sessionStorage.id = res.data.id;
sessionStorage.credit = res.data.credit;
sessionStorage.credit_to_money = res.data.credit_to_money;
localStorage.removeItem('token');
localStorage.removeItem('username');
localStorage.removeItem('id');
localStorage.removeItem('credit');
localStorage.removeItem('credit_to_money');
}
this.$confirm('下一步想去哪消费!', '提示', {
confirmButtonText: '去首页',
cancelButtonText: '回到上一页',
type: 'success'
}).then(() => {
this.$router.push('/');
}).catch(() => {
this.$router.go(-1);
});
}).catch((error)=>{
this.$alert('用户名或者密码错误', '登录失败', {
confirmButtonText: '确定',
});
})
}
});
captcha1.show(); // 显示验证码
}
...
4.用户可以设置抵扣积分
在用户购买课程进入计算页面时把积分显示并让用户可以设置要抵扣的积分金额
Orde.vue
<template>
<div class="cart">
<Vheader/>
<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,index) in course_list">
<el-row>
<el-col :span="2" class="checkbox"> </el-col>
<el-col :span="10" class="course-info">
<img :src="course.course_img" alt="">
<span>{{course.name}}</span>
</el-col>
<el-col :span="8"><span>{{course.expire_text}}</span></el-col>
<el-col :span="4" class="course-price">¥{{course.real_price}}</el-col>
</el-row>
</div>
<div class="discount">
<div id="accordion">
<div class="coupon-box">
<div class="icon-box">
<span class="select-coupon">使用优惠劵:</span>
<a class="select-icon unselect" :class="use_coupon?'is_selected':''" @click="use_coupon=!use_coupon"><img class="sign is_show_select" src="../../static/img/12.png" alt=""></a>
<span class="coupon-num">有{{coupon_list.length}}张可用</span>
</div>
<p class="sum-price-wrap">商品总金额:<span class="sum-price">0.00元</span></p>
</div>
<div id="collapseOne" v-if="use_coupon">
<ul class="coupon-list" v-if="coupon_list.length>0">
<li class="coupon-item" :class="select_coupon(index,coupon.id)" v-for="(coupon,index) in coupon_list" @click="change_coupon(index,coupon.id)">
<p class="coupon-name">{{coupon.coupon.name}}</p>
<p class="coupon-condition">满{{coupon.coupon.condition}}元可以使用</p>
<p class="coupon-time start_time">开始时间:{{coupon.start_time.replace('T',' ')}}</p>
<p class="coupon-time end_time">过期时间:{{coupon.end_time.replace('T',' ')}}</p>
</li>
</ul>
<div class="no-coupon" v-if="coupon_list.length<1">
<span class="no-coupon-tips">暂无可用优惠券</span>
</div>
</div>
</div>
<div class="credit-box">
<label class="my_el_check_box"><el-checkbox class="my_el_checkbox" v-model="use_credit"></el-checkbox></label>
<p class="discount-num1" v-if="!use_credit">使用我的贝里</p>
<p class="discount-num2" v-else><span>总积分:{{credit}},
<el-input-number v-model="num" @change="handleChange" :min="0" :max="max_credit()" label="描述文字"></el-input-number>,已抵扣 ¥{{num / credit_to_money}},本次花费{{num}}积分</span></p>
</div>
<p class="sun-coupon-num">优惠券抵扣:<span>0.00元</span></p>
</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" v-if="pay_type===0"><img src="../../static/img/alipay2.png" alt=""></span>
<span class="alipay" @click="pay_type=0" v-else><img src="../../static/img/alipay.png" alt=""></span>
<span class="alipay wechat" v-if="pay_type===1"><img src="../../static/img/wechat2.png" alt="" ></span>
<span class="alipay wechat" @click="pay_type=1" v-else><img src="../../static/img/wechat.png" alt=""></span>
</el-col>
<el-col :span="8" class="count">实付款: <span>¥{{ (total_price - num/credit_to_money).toFixed(2)}}</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 Vheader from "./common/Vheader"
import Footer from "./common/Footer"
export default {
name:"Order",
data(){
return {
id: localStorage.id || sessionStorage.id,
order_id:sessionStorage.order_id || null,
course_list: [],
coupon_list: [],
total_price: 0, //加上优惠券和积分计算之后的真实总价
total_real_price: 0,
pay_type:0,
num:0,
current_coupon:0, // 当前选中的优惠券id
use_coupon:false,
use_credit:false,
coupon_obj:{}, // 当前coupon对象 {}
credit:0, // 用户剩余积分
credit_to_money:0, //积分兑换比例
}
},
components:{
Vheader,
Footer,
},
watch:{
use_coupon(){
if (this.use_coupon === false){
this.current_coupon = 0;
}
},
//当选中的优惠券发生变化时,重新计算总价
current_coupon(){
this.cal_total_price();
}
},
created(){
this.get_order_data();
this.get_user_coupon();
this.get_credit();
},
methods: {
max_credit(){
let a = parseFloat(this.total_price) * parseFloat(this.credit_to_money);
if (this.credit >= a){
return a
}else {
return parseFloat(this.credit)
}
},
get_credit(){
this.credit = localStorage.getItem('credit') || sessionStorage.getItem('credit')
this.credit_to_money = localStorage.getItem('credit_to_money') || sessionStorage.getItem('credit_to_money')
},
handleChange(value){
if (!value){
this.num = 0
}
console.log(value);
},
cal_total_price(){
if (this.current_coupon !== 0){
let tt = this.total_real_price;
let sales = this.coupon_obj.coupon.sale;
let d = parseFloat(this.coupon_obj.coupon.sale.substr(1));
if (sales[0] === '-'){
tt = this.total_real_price - d
// this.total_real_price -= d
}else if (sales[0] === '*'){
// this.total_real_price *= d
tt = this.total_real_price * d
}
this.total_price = tt;
}
},
// 记录切换的couponid
change_coupon(index,coupon_id){
let current_c = this.coupon_list[index]
if (this.total_real_price < current_c.coupon.condition){
return false
}
let current_time = new Date() / 1000;
let s_time = new Date(current_c.start_time) / 1000
let e_time = new Date(current_c.end_time) / 1000
if (current_time < s_time || current_time > e_time){
return false
}
this.current_coupon = coupon_id;
this.coupon_obj = current_c;
},
select_coupon(index,coupon_id){
// {
// "id": 2,
// "start_time": "2020-11-10T09:03:00",
// "coupon": {
// "name": "五十元优惠券",
// "coupon_type": 1,
// "timer": 30,
// "condition": 50,
// "sale": "-50"
// },
// "end_time": "2020-12-10T09:03:00"
// },
let current_c = this.coupon_list[index]
if (this.total_real_price < current_c.coupon.condition){
return 'disable'
}
let current_time = new Date() / 1000;
let s_time = new Date(current_c.start_time) / 1000
let e_time = new Date(current_c.end_time) / 1000
if (current_time < s_time || current_time > e_time){
return 'disable'
}
if (this.current_coupon === coupon_id){
return 'active'
}
return ''
},
get_order_data(){
let token = localStorage.token || sessionStorage.token;
this.$axios.get(`${this.$settings.Host}/cart/expires/`,{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.course_list = res.data.data;
this.total_real_price = res.data.total_real_price;
this.total_price = res.data.total_real_price;
})
},
get_user_coupon(){
let token = localStorage.token || sessionStorage.token;
this.$axios.get(`${this.$settings.Host}/coupon/list/`,{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.coupon_list = res.data;
}).catch((error)=>{
this.$message.error('优惠券获取错误')
})
},
// 支付
payhander(){
let token = localStorage.token || sessionStorage.token;
this.$axios.post(`${this.$settings.Host}/order/add_money/`,{
"pay_type":this.pay_type,
"coupon":this.current_coupon,
"credit":this.num,
},{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.$message.success('订单已经生成,马上跳转支付页面')
let order_number = res.data.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)=>{
// res.data : alipay.trade...?a=1&b=2....
location.href = res.data.url;
})
}).catch((error)=>{
this.$message.error(error.response.data.msg);
})
}
}
}
</script>
<style scoped>
.coupon-box{
text-align: left;
padding-bottom: 22px;
padding-left:30px;
border-bottom: 1px solid #e8e8e8;
}
.coupon-box::after{
content: "";
display: block;
clear: both;
}
.icon-box{
float: left;
}
.icon-box .select-coupon{
float: left;
color: #666;
font-size: 16px;
}
.icon-box::after{
content:"";
clear:both;
display: block;
}
.select-icon{
width: 20px;
height: 20px;
float: left;
}
.select-icon img{
max-height:100%;
max-width: 100%;
margin-top: 2px;
transform: rotate(-90deg);
transition: transform .5s;
}
.is_show_select{
transform: rotate(0deg)!important;
}
.coupon-num{
height: 22px;
line-height: 22px;
padding: 0 5px;
text-align: center;
font-size: 12px;
float: left;
color: #fff;
letter-spacing: .27px;
background: #fa6240;
border-radius: 2px;
margin-left: 20px;
}
.sum-price-wrap{
float: right;
font-size: 16px;
color: #4a4a4a;
margin-right: 45px;
}
.sum-price-wrap .sum-price{
font-size: 18px;
color: #fa6240;
}
.no-coupon{
text-align: center;
width: 100%;
padding: 50px 0px;
align-items: center;
justify-content: center; /* 文本两端对其 */
border-bottom: 1px solid rgb(232, 232, 232);
}
.no-coupon-tips{
font-size: 16px;
color: #9b9b9b;
}
.credit-box{
height: 30px;
margin-top: 40px;
display: flex;
align-items: center;
justify-content: flex-end
}
.my_el_check_box{
position: relative;
}
.my_el_checkbox{
margin-right: 10px;
width: 16px;
height: 16px;
}
.discount{
overflow: hidden;
}
.discount-num1{
color: #9b9b9b;
font-size: 16px;
margin-right: 45px;
}
.discount-num2{
margin-right: 45px;
font-size: 16px;
color: #4a4a4a;
}
.sun-coupon-num{
margin-right: 45px;
margin-bottom:43px;
margin-top: 40px;
font-size: 16px;
color: #4a4a4a;
display: inline-block;
float: right;
}
.sun-coupon-num span{
font-size: 18px;
color: #fa6240;
}
.coupon-list{
margin: 20px 0;
}
.coupon-list::after{
display: block;
content:"";
clear: both;
}
.coupon-item{
float: left;
margin: 15px 8px;
width: 180px;
height: 100px;
padding: 5px;
background-color: #fa3030;
cursor: pointer;
}
.coupon-list .active{
background-color: #fa9000;
}
.coupon-list .disable{
cursor: not-allowed;
background-color: #fa6060;
}
.coupon-condition{
font-size: 12px;
text-align: center;
color: #fff;
}
.coupon-name{
color: #fff;
font-size: 24px;
text-align: center;
}
.coupon-time{
text-align: left;
color: #fff;
font-size: 12px;
}
.unselect{
margin-left: 0px;
transform: rotate(-90deg);
}
.is_selected{
transform: rotate(-1turn)!important;
}
.coupon-item p{
margin: 0;
padding: 0;
}
.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;
margin-bottom: 30px;
}
.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>
5.后端抵扣积分接口
在用户确定确认下单时, 在序列化器中计算实付金额时纳入积分抵扣
order/serializers.py
import datetime
from rest_framework import serializers
from . import models
from django_redis import get_redis_connection
from users.models import User
from course.models import Course
from course.models import CourseExpire
from coupon.models import UserCoupon, Coupon
from django.db import transaction
from lyapi.settings import contains
class OrderModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Order
fields = ['id', 'order_number', 'pay_type', 'coupon', 'credit']
extra_kwargs = {
'id':{'read_only':True},
'order_number':{'read_only':True},
'pay_type':{'write_only':True},
'coupon':{'write_only':True},
'credit':{'write_only':True},
}
def validate(self, attrs):
# 支付方式
pay_type = int(attrs.get('pay_type',0)) #
if pay_type not in [i[0] for i in models.Order.pay_choices]:
raise serializers.ValidationError('支付方式不对!')
# 优惠券校验
coupon_id = attrs.get('coupon', 0)
if coupon_id > 0:
try:
user_conpon_obj = UserCoupon.objects.get(id=coupon_id)
except:
raise serializers.ValidationError('订单创建失败,优惠券id不对')
# condition = user_conpon_obj.coupon.condition
now = datetime.datetime.now().timestamp()
start_time = user_conpon_obj.start_time.timestamp()
end_time = user_conpon_obj.end_time.timestamp()
if now < start_time or now > end_time:
raise serializers.ValidationError('订单创建失败,优惠券不在使用范围内,滚犊子')
# 积分上限校验
credit = attrs.get('credit')
user_credit = self.context['request'].user.credit
if credit > user_credit:
raise serializers.ValidationError('积分超上限了,别乱搞')
return attrs
def create(self, validated_data):
try:
# 生成订单号 [日期,用户id,自增数据]
current_time = datetime.datetime.now()
now = current_time.strftime('%Y%m%d%H%M%S')
user_id = self.context['request'].user.id
conn = get_redis_connection('cart')
num = conn.incr('num')
order_number = now + "%06d" % user_id + "%06d" % num
total_price = 0 # 总原价
total_real_price = 0 # 总真实价格
with transaction.atomic(): # 添加事务
sid = transaction.savepoint() # 创建事务保存点
# 生成订单
order_obj = models.Order.objects.create(**{
'order_title': '31期订单',
'total_price': 0,
'real_price': 0,
'order_number': order_number,
'order_status': 0,
'pay_type': validated_data.get('pay_type', 0),
'credit': 0,
'coupon': 0,
'order_desc': '女朋友',
'pay_time': current_time,
'user_id': user_id,
# 'user':user_obj,
})
select_list = conn.smembers('selected_cart_%s' % user_id)
ret = conn.hgetall('cart_%s' % user_id) # dict {b'1': b'0', b'2': b'0'}
for cid, eid in ret.items():
expire_id = int(eid.decode('utf-8'))
if cid in select_list:
course_id = int(cid.decode('utf-8'))
course_obj = Course.objects.get(id=course_id)
# expire_text = '永久有效'
if expire_id > 0:
expire_text = CourseExpire.objects.get(id=expire_id).expire_text
# 生成订单详情
models.OrderDetail.objects.create(**{
'order': order_obj,
'course': course_obj,
'expire': expire_id,
'price': course_obj.price,
'real_price': course_obj.real_price(expire_id),
'discount_name': course_obj.discount_name(),
})
total_price += course_obj.price
total_real_price += course_obj.real_price(expire_id)
coupon_id = validated_data.get('coupon')
# condition = 100
# if coupon_id > 0:
try:
user_conpon_obj = UserCoupon.objects.get(id=coupon_id)
except:
transaction.savepoint_rollback(sid)
raise serializers.ValidationError('订单创建失败,优惠券id不对')
condition = user_conpon_obj.coupon.condition
if total_real_price < condition:
transaction.savepoint_rollback(sid)
raise serializers.ValidationError('订单创建失败,优惠券不满足使用条件')
sale = user_conpon_obj.coupon.sale
if sale[0] == '-':
total_real_price -= float(sale[1:])
elif sale[0] == '*':
total_real_price *= float(sale[1:])
order_obj.coupon = coupon_id
# 积分判断
credit = float(validated_data.get('credit',0))
if credit > contains.CREDIT_MONEY * total_real_price:
transaction.savepoint_rollback(sid)
raise serializers.ValidationError('使用积分超过了上线,别高事情')
# 积分计算
total_real_price -= credit / contains.CREDIT_MONEY
order_obj.credit = credit
order_obj.total_price = total_price
order_obj.real_price = total_real_price
order_obj.save()
# 结算成功之后,再清除
# conn = get_redis_connection('cart')
# conn.delete('selected_cart_%s' % user_id)
# print('xxxxx')
except Exception:
raise models.Order.DoesNotExist
# order_obj.order_number = order_number
return order_obj
二、发起支付
支付宝开发平台
https://open.alipay.com/platform/home.htm
1.沙箱环境
-
是支付宝提供给开发者的模拟支付的环境
-
沙箱环境跟真实环境是分开的,项目上线时必须切换对应的配置服务器地址和开发者ID和密钥。
-
沙箱账号:https://openhome.alipay.com/platform/appDaily.htm?tab=account
真实的支付宝网关: https://openapi.alipay.com/gateway.do 沙箱的支付宝网关: https://openapi.alipaydev.com/gateway.do
2.支付宝开发者文档
文档主页:https://openhome.alipay.com/developmentDocument.htm
产品介绍:https://docs.open.alipay.com/270
3.开发支付功能
cd lyapi/apps
python ../../manage.py startapp payment
注册子应用
INSTALLED_APPS = [
...
'payment',
]
4.配置秘钥
(1)生成应用的私钥和公钥
下载对应系统的秘钥生成工具: https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1
windows操作系统
生成如下,安装软件时需要管理员身份来安装.
![](https://i-blog.csdnimg.cn/blog_migrate/9c8acee030cb4a969e71f76daf49118b.png#pic_center)
Linux系统
生成如下:
openssl
OpenSSL> genrsa -out app_private_key.pem 2048 # 生成私钥到指定文件中
OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥
OpenSSL> exit
把软件生成的应用公钥复制粘贴到支付宝网站页面中.
点击修改以后,粘贴进去生成支付宝公钥。
![](https://i-blog.csdnimg.cn/blog_migrate/657f50c89d735a63d38ecd070a1e1e5b.png#pic_center)
(2)保存应用私钥文件
在payment应用中新建keys目录,用来保存秘钥文件。
将应用私钥文件app_private_key.pem复制到payment/keys目录下。
windows系统生成的私钥必须在上下两行加上以下标识:
-----BEGIN RSA PRIVATE KEY-----
私钥
-----END RSA PRIVATE KEY-----
(3)保存支付宝公钥到项目中
在payment/key目录下新建alipay_public_key.pem文件,用于保存支付宝的公钥文件。
将支付宝的公钥内容复制到alipay_public_key.pem文件中
-----BEGIN PUBLIC KEY-----
公钥
-----END PUBLIC KEY-----
(4)使用支付宝的sdk开发支付接口
SDK:https://docs.open.alipay.com/270/106291/
python版本的支付宝SDK文档:https://github.com/fzlee/alipay/blob/master/README.zh-hans.md
安装命令:
pip install python-alipay-sdk --upgrade
5.后端提供发起支付的接口url地址
总urls.py
...
path(r'payment/',include('payment.urls')),
...
payment/urls.py
from django.urls import path,re_path
from . import views
urlpatterns = [
path('alipay/',views.AlipayView.as_view(),),
path('result/',views.AlipayResultView.as_view(),)
]
payment/views.py
from django.shortcuts import render
import datetime
from rest_framework.views import APIView
# Create your views here.
from alipay import AliPay, DCAliPay, ISVAliPay
from alipay.utils import AliPayConfig
from django.conf import settings
from rest_framework.response import Response
from order.models import Order
from users.models import User, UserCourse
from course.models import CourseExpire
from rest_framework import status
from coupon.models import UserCoupon
from rest_framework.permissions import IsAuthenticated
from django.db import transaction
from django_redis import get_redis_connection
import logging
logger = logging.getLogger('django')
class AlipayView(APIView):
permission_classes = [IsAuthenticated, ]
def get(self,request):
order_number = request.query_params.get('order_number')
order_obj = Order.objects.get(order_number=order_number)
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
)
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order_obj.order_number,
total_amount=float(order_obj.real_price),
subject=order_obj.order_title,
return_url=settings.ALIAPY_CONFIG['return_url'],
notify_url=settings.ALIAPY_CONFIG['notify_url'] # 可选, 不填则使用默认notify url
)
url = settings.ALIAPY_CONFIG['gateway_url'] + order_string
return Response({'url': url})
class AlipayResultView(APIView):
permission_classes = [IsAuthenticated, ]
def get(self,request):
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.query_params.dict()
out_trade_no = data.get('out_trade_no')
sign = data.pop('sign')
success = alipay.verify(data,sign)
print('status>>>',success)
if not success:
logger.error('%s,支付宝响应数据校验失败' % out_trade_no)
return Response('支付宝响应数据校验失败',status=status.HTTP_400_BAD_REQUEST)
res_data = self.change_order_status(data)
# 响应结果
return Response({'msg':'这一脚没毛病','data':res_data})
def post(self,request):
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')
def change_order_status(self,data):
with transaction.atomic():
out_trade_no = data.get('out_trade_no')
trade_no = data.get('trade_no')
# 修改订单状态
# try:
order_obj = Order.objects.get(order_number=out_trade_no)
order_obj.order_status = 1
order_obj.save()
# except Exception as e:
# e 如何获取错误信息文本内容
# return Response({'msg':'订单生成失败,没有找到该订单'},status=status.HTTP_400_BAD_REQUEST)
# 修改优惠券的使用状态
print('order_obj.coupon', order_obj.coupon)
if order_obj.coupon > 0:
user_coupon_obj = UserCoupon.objects.get(is_use=False, id=order_obj.coupon)
user_coupon_obj.is_use = True
user_coupon_obj.save()
# 扣积分
use_credit = order_obj.credit
self.request.user.credit -= use_credit
self.request.user.save()
# 保存支付宝的交易流水号(购买记录表)
order_detail_objs = order_obj.order_courses.all()
now = datetime.datetime.now()
conn = get_redis_connection('cart')
pipe = conn.pipeline()
pipe.delete('selected_cart_%s' % self.request.user.id)
res_data = {
'pay_time': now,
'course_list': [],
'total_real_price': order_obj.real_price,
}
for order_detail in order_detail_objs:
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_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.vue
...
payhander(){
let token = localStorage.token || sessionStorage.token;
this.$axios.post(`${this.$settings.Host}/order/add_money/`,{
"pay_type":this.pay_type,
"coupon":this.current_coupon,
"credit":this.num,
},{
headers:{
'Authorization':'jwt ' + token
}
}).then((res)=>{
this.$message.success('订单已经生成,马上跳转支付页面')
let order_number = res.data.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)=>{
// res.data : alipay.trade...?a=1&b=2....
location.href = res.data.url;
})
}).catch((error)=>{
this.$message.error(error.response.data.msg);
})
}
...
settings/dev.py
# 支付宝配置信息
ALIAPY_CONFIG = {
# "gateway_url": "https://openapi.alipay.com/gateway.do?", # 真实支付宝网关地址
"gateway_url": "https://openapi.alipaydev.com/gateway.do?", # 沙箱支付宝网关地址
"appid": "2016110100783756", # 沙箱中那个应用id
"app_notify_url": None,
"app_private_key_path": os.path.join(BASE_DIR, "apps/payment/keys/app_private_key.pem"),
"alipay_public_key_path": os.path.join(BASE_DIR, "apps/payment/keys/alipay_public_key.pem"),
"sign_type": "RSA2",
"debug": False,
"return_url": "http://www.lycity.com:8080/payment/result", # 同步回调地址
"notify_url": "http://www.lyapi.com:8001/payment/result/", # 异步结果通知
}