文章目录
支付-支付宝
支付宝开发平台登录
https://open.alipay.com/platform/home.htm
沙箱环境
-
是支付宝提供给开发者的模拟支付的环境
-
沙箱环境跟真实环境是分开的,项目上线时必须切换对应的配置服务器地址和开发者ID和密钥。
-
沙箱应用开发文档:https://docs.open.alipay.com
-
沙箱账号:https://openhome.alipay.com/platform/appDaily.htm?tab=account>
真实的支付宝网关: https://openapi.alipay.com/gateway.do
沙箱的支付宝网关: https://openapi.alipaydev.com/gateway.do
支付宝开发者文档
电脑网站支付流程
时序图[ 时间顺序流程图 ]
开发支付功能
cd luffyapi/apps
python ../../manage.py startapp payments
注册子应用
INSTALLED_APPS = [
...
'payments',
]
配置秘钥
1. 生成应用的私钥和公钥
下载对应系统的秘钥生成工具: https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1
windows操作系统
生成如下,安装软件时需要管理员身份来安装.
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
应用公钥复制粘贴到支付宝网站页面中.
点击修改以后,粘贴进去
2. 保存应用私钥文件
在payments
应用中新建keys
目录,用来保存秘钥文件。
将应用私钥文件app_private_key.pem
复制到payment/keys
目录下。
windows系统生成的私钥必须在上下两行加上以下标识:
-----BEGIN RSA PRIVATE KEY-----
私钥
-----END RSA PRIVATE KEY-----
3. 保存支付宝公钥到项目中
在payments/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
后端提供发起支付的接口url地址
from rest_framework.views import APIView
from alipay import AliPay
from django.conf import settings
from orders.models import Order
from rest_framework.response import Response
from rest_framework import status
class AliapyAPIView(APIView):
def post(self,request,order_number):
"""生成支付宝支付链接的地址"""
# 接受订单信息
try:
# order_status=0 表示未支付宝
order = Order.objects.get(order_number=order_number,order_status=0)
except Order.DoesNotExist:
return Response({"message":"对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
# 创建支付宝的sdk对象
alipay = AliPay(
appid= settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug = settings.ALIAPY_CONFIG["debug"], # 默认False
)
# 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order.order_number, # 订单号
total_amount=float(order.real_price), # 订单总金额[单位:元]
subject=order.order_title, # 订单标题
return_url=settings.ALIAPY_CONFIG["return_url"], # 同步通知地址
notify_url=settings.ALIAPY_CONFIG["notify_url"], # 异步通知地址
)
pay_url = settings.ALIAPY_CONFIG["gateway_url"] + order_string
return Response({"pay_url":pay_url})
在配置文件中编辑支付宝的配置信息[实际的值根据自己的账号而定]
setttins/dev.py
,代码:
# 支付宝配置信息
ALIAPY_CONFIG = {
# "gateway_url": "https://openapi.alipay.com/gateway.do?", # 真实支付宝网关地址
"gateway_url": "https://openapi.alipaydev.com/gateway.do?", # 沙箱支付宝网关地址
"appid": "20*********3592",
"app_notify_url": None,
"app_private_key_path": os.path.join(os.path.dirname(BASE_DIR), "luffyapi/apps/payments/key/app_private_key.pem"),
"alipay_public_key_path": os.path.join(os.path.dirname(BASE_DIR), "luffyapi/apps/payments/key/alipay_public_key.pem"),
"sign_type": "RSA2",
"debug": False,
"return_url": "http://www.luffycity.cn:8080/pay/result",
"notify_url": "http://api.luffycity.cn:8000/pay/result",
}
注册url地址:payments/urls.py
,代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
re_path(r'(?P<order_number>\d+)/alipay/', views.AliapyAPIView.as_view() ),
]
总路由,代码:
path('payments/', include("payments.urls")),
前端点击"支付宝支付",会生成订单,生成订单返回订单号以后,再次请求后端的获取支付api的url地址
<template>
<div class="cart">
...
</el-col>
<el-col :span="8" class="count">实付款: <span>¥{{(real_total - credit/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 Header from "./common/Header"
import Footer from "./common/Footer"
export default {
name:"Order",
data(){
return {
user_credit: localStorage.user_credit || sessionStorage.user_credit, // 用户积分
credit_to_money: localStorage.credit_to_money || sessionStorage.credit_to_money, // 积分换算比例
course_list:[], // 勾选商品
pay_type: 1, // 支付方式
use_credit: false, // 是否使用了优惠券
credit: 0, // 积分
use_coupon: false, // 优惠券ID,0表示没有使用优惠券
coupon: 0, // 优惠券ID,0表示没有使用优惠券
coupon_list:[], // 优惠券列表
total: 0, // 购物车中商品总金额
real_total: 0, // 实付金额
}
},
components:{
Header,
Footer,
},
created(){
this.check_user_login();
this.get_selected_course();
this.get_user_coupon();
},
watch:{
coupon(){
// 每次用户优惠券时,总价格都要重新计算
this.total = this.get_total();
this.real_total = this.get_total(true);
},
use_coupon(){
if(!this.use_coupon){
// 当用户不使用优惠券时,把用户当前选择的优惠券ID重置为0
this.coupon = 0;
}
// 每次用户优惠券时,总价格都要重新计算
this.total = this.get_total();
this.real_total = this.get_total(true);
},
use_credit(){
if(!this.use_credit){
// 地方用户不适用积分抵扣时,把用户当前设置的抵扣积分进行重置
this.credit = 0;
}
// 每次用户优惠券时,总价格都要重新计算
this.total = this.get_total();
this.real_total = this.get_total(true);
}
},
methods: {
...
payHander(){
// 生成订单
this.$axios.post(`${this.$settings.Host}/orders/`,{
pay_type: this.pay_type,
credit: this.credit,
coupon: this.coupon
},{
headers:{
"Authorization":"jwt " + this.check_user_login(),
}
}).then(response=>{
// 下单成功,获取支付链接
this.get_alipay_payment_url(response.data.order_number);
}).catch(error=>{
console.log(error.response);
})
},
get_alipay_payment_url(order_number){
// 获取支付宝的支付地址
this.$axios.post(`${this.$settings.Host}/payments/${order_number}/alipay/`).then(response=>{
console.log(response.data.pay_url);
// 页面跳转
location.href=response.data.pay_url;
}).catch(error=>{
console.log(error.response);
})
}
}
}
</script>
完成了上面的功能以后,我们就可以在沙箱环境中进行支付宝的付款了,但是我们会接受到支付宝界面那边跳转回来的同步通知结果,跳转回到我们的客户端页面,
返回的结果.,我们不能保证一定是支付宝返回的,所以我们需要对照官方文档,查看这些参数的作用,
https://docs.open.alipay.com/api_1/alipay.trade.page.pay
http://www.luffycity.cn:8080/pay/result?charset=utf-8&out_trade_no=201908231722270000013680&method=alipay.trade.page.pay.return&total_amount=809.10&sign=D%2BjTb1QHIG5OGrCMgzDAkb6FhgbvuwFMAGHZ6TpCTJyk7EhpzdxXYNX5MLJzYmveGkcjC59n7HrgTGz1vIa3LsMdzTL6ixxHLtE03hFQX4z6voSNmHkic3GRuNGn1hBN0OJLKUsLax%2FxA24DY8UUZ7%2FndQizAC5wUQtMRatG0Jx1z5gaN1xB0u5sEnTSHfHLqaUgIR8Bg8hHT8KAkTuxkn7kOZOORDhICTPkKizgy0E%2F6bjIupbG6qC1aKpUrsokz6BdjbORtQntpN9zZPOZzCzejvjKGrdgXnYc3tQeBXU%2Fch7y3LYol0OW5ickYBuZkXLhNAjsUG9yX1EO0Tnw2w%3D%3D&trade_no=2019082322001439881000079299&auth_app_id=2016091600523592&version=1.0&app_id=2016091600523592&sign_type=RSA2&seller_id=2088102175868026×tamp=2019-08-23%2017%3A24%3A37
支付成功的模板
Success.vue
,代码;
<template>
<div class="success">
<Header/>
<div class="main">
<div class="title">
<!-- <img src="../../static/images/right.svg" alt="">-->
<div class="success-tips">
<p class="tips1">您已成功购买 1 门课程!</p>
<p class="tips2">你还可以加入QQ群 <span>747556033</span> 学习交流</p>
</div>
</div>
<div class="order-info">
<p class="info1"><b>付款时间:</b><span>2019/04/02 10:27</span></p>
<p class="info2"><b>付款金额:</b><span >0</span></p>
<p class="info3"><b>课程信息:</b><span><span>《Pycharm使用秘籍》</span></span></p>
</div>
<div class="wechat-code">
<!-- <img src="../../static/image/server.cf99f78.png" alt="" class="er">-->
<!-- <p><img src="../../static/image/tan.svg" alt="">重要!微信扫码关注获得学习通知&课程更新提醒!否则将严重影响学习进度和课程体验!</p>-->
</div>
<div class="study">
<span>立即学习</span>
</div>
</div>
<Footer/>
</div>
</template>
<script>
import Header from "./common/Header"
import Footer from "./common/Footer"
export default{
name:"Success",
data(){
return {
current_page:0,
};
},
created(){
// 把地址栏上面的支付结果,转发给后端
},
components:{
Header,
Footer,
}
}
</script>
<style scoped>
.success{
padding-top: 80px;
}
.main{
height: 100%;
padding-top: 25px;
padding-bottom: 25px;
margin: 0 auto;
width: 1200px;
background: #fff;
}
.main .title{
display: flex;
-ms-flex-align: center;
align-items: center;
padding: 25px 40px;
border-bottom: 1px solid #f2f2f2;
}
.main .title .success-tips{
box-sizing: border-box;
}
.title img{
vertical-align: middle;
width: 60px;
height: 60px;
margin-right: 40px;
}
.title .success-tips{
box-sizing: border-box;
}
.title .tips1{
font-size: 22px;
color: #000;
}
.title .tips2{
font-size: 16px;
color: #4a4a4a;
letter-spacing: 0;
text-align: center;
margin-top: 10px;
}
.title .tips2 span{
color: #ec6730;
}
.order-info{
padding: 25px 48px;
padding-bottom: 15px;
border-bottom: 1px solid #f2f2f2;
}
.order-info p{
display: -ms-flexbox;
display: flex;
margin-bottom: 10px;
font-size: 16px;
}
.order-info p b{
font-weight: 400;
color: #9d9d9d;
white-space: nowrap;
}
.wechat-code{
display: flex;
-ms-flex-align: center;
align-items: center;
padding: 25px 40px;
border-bottom: 1px solid #f2f2f2;
}
.wechat-code>img{
width: 100px;
height: 100px;
margin-right: 15px;
}
.wechat-code p{
font-size: 14px;
color: #d0021b;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
}
.wechat-code p>img{
width: 16px;
height: 16px;
margin-right: 10px;
}
.study{
padding: 25px 40px;
}
.study span{
display: block;
width: 140px;
height: 42px;
text-align: center;
line-height: 42px;
cursor: pointer;
background: #ffc210;
border-radius: 6px;
font-size: 16px;
color: #fff;
}
</style>
路由router.index.js
:
{
name: 'Success',
path: '/pay/result',
component: Success
},
后端接受支付结果
支付宝会返回的参数如下列表:
http://www.luffycity.cn:8080?
charset=utf-8&
out_trade_no=2019040217080000000010976&
method=alipay.trade.page.pay.return&
total_amount=1206.44&
sign=XKJG5826fH%2F9%2B3jCWw2ODjlc%2FuGLfqmr5RnimSAqrh%2B5bFkWcbLDh5V6VYtMqCpwnYp3FuGPqEeUeRO6WK62Qz0Q5nQGOA394IdxPfTOzry7PXuwYf41PCbDq53yg7vCYrobz4Tt8uajeADJLJwIsL%2F%2B88vbDEISUDUujL4442kl3oLh3EDD8DxZc2LLsv1Z%2FEFGJMfcTA47A4T7qmjB%2BbLKJetZZBISdt9RDL0q8A%2BAfb8B3Ux1nq%2F0EiNGiwIlWC1pvUCHK2UXMJW3kmgU9P9Zoujrj4ER28oieQt6Rt4gQXeah5uYtAMkftWfZpiyu%2FjUkr6iRx%2B4mP5IFz4Uew%3D%3D&
trade_no=2019040222001439881000005802&
auth_app_id=2016091600523592&
version=1.0&
app_id=2016091600523592&
sign_type=RSA2&
seller_id=2088102175868026&
timestamp=2019-04-02%2017%3A13%3A15
后端实现处理支付宝通知的视图,代码:
payments/views.py
from rest_framework.views import APIView
from alipay import AliPay
from django.conf import settings
from orders.models import Order
from rest_framework.response import Response
from rest_framework import status
from datetime import datetime
from coupon.models import UserCoupon
from django.db import transaction
import logging
log = logging.getLogger("django")
class AliapyAPIView(APIView):
def post(self,request,order_number):
"""生成支付宝支付链接的地址"""
# 接受订单信息
try:
# order_status=0 表示未支付宝
order = Order.objects.get(order_number=order_number,order_status=0)
except Order.DoesNotExist:
return Response({"message":"对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
# 创建支付宝的sdk对象
alipay = AliPay(
appid= settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug = settings.ALIAPY_CONFIG["debug"], # 默认False
)
# 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order.order_number, # 订单号
total_amount=float(order.real_price), # 订单总金额[单位:元]
subject=order.order_title, # 订单标题
return_url=settings.ALIAPY_CONFIG["return_url"], # 同步通知地址
notify_url=settings.ALIAPY_CONFIG["notify_url"], # 异步通知地址
)
pay_url = settings.ALIAPY_CONFIG["gateway_url"] + order_string
return Response({"pay_url":pay_url})
class AlipayResultAPIView(APIView):
"""
支付宝支付结果的通知处理
"""
def get(self,request):
# for rest_framework users
data = request.query_params.dict()
signature = data.pop("sign")
# 创建支付宝的sdk对象
alipay = AliPay(
appid= settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug = settings.ALIAPY_CONFIG["debug"], # 默认False
)
# verification
success = alipay.verify(data, signature)
if success:
# 修改订单状态
out_trade_no = data.get("out_trade_no")
try:
order = Order.objects.get(order_number=out_trade_no, order_status=0)
except Order.DoesNotExist:
return Response({"message": "对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
# 记录事务的回滚点
save_id = transaction.savepoint()
order.order_status = 1
order.pay_time = datetime.now()
order.save()
# 如果订单中使用了优惠券,则优惠券的使用状态要调整
if order.coupon > 0:
user_coupon_id = order.coupon
try:
user_coupon = UserCoupon.objects.get(pk=user_coupon_id,is_use=False)
user_coupon.is_use = True
user_coupon.save()
except UserCoupon.DoesNotExist:
log.error("生成订单支付结果有误!优惠券发生异常!")
transaction.savepoint_rollback(save_id)
# 如果用户使用了积分,则扣除相应积分
if order.credit > 0:
user = order.user
user.credit = user.credit - order.credit
if user.credit > 0:
user.save()
else:
log.error("生成订单支付结果有误!积分计算有误!")
transaction.savepoint_rollback(save_id)
# 记录用户购买商品的记录信息
data = {
"order_number": order.order_number,
"pay_time": order.pay_time,
"real_price": order.real_price,
}
return Response(data)
else:
return Response({"message":"支付失败!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def post(self,request):
return Response({"message":"ok"})
后端完成 支付宝支付结果的处理并更新订单和购买记录
users/models.py
,模型代码:
from courses.models import Course
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="支付平台的账单号")
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="过期时间")
class Meta:
db_table = 'ly_user_course'
verbose_name = '课程购买记录'
verbose_name_plural = verbose_name
数据迁移
python manage.py makemigrations
python manage.py migrate
实现同步结果通知的API接口
payment/views.py
,视图代码:
from rest_framework.views import APIView
from alipay import AliPay
from django.conf import settings
from orders.models import Order
from rest_framework.response import Response
from rest_framework import status
from datetime import datetime
from coupon.models import UserCoupon
from django.db import transaction
from users.models import UserCourse
from courses.models import CourseExpire
from users.models import User
import logging
log = logging.getLogger("django")
class AliapyAPIView(APIView):
def post(self,request,order_number):
"""生成支付宝支付链接的地址"""
# 接受订单信息
try:
# order_status=0 表示未支付宝
order = Order.objects.get(order_number=order_number,order_status=0)
except Order.DoesNotExist:
return Response({"message":"对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
# 创建支付宝的sdk对象
alipay = AliPay(
appid= settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug = settings.ALIAPY_CONFIG["debug"], # 默认False
)
# 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order.order_number, # 订单号
total_amount=float(order.real_price), # 订单总金额[单位:元]
subject=order.order_title, # 订单标题
return_url=settings.ALIAPY_CONFIG["return_url"], # 同步通知地址
notify_url=settings.ALIAPY_CONFIG["notify_url"], # 异步通知地址
)
pay_url = settings.ALIAPY_CONFIG["gateway_url"] + order_string
return Response({"pay_url":pay_url})
class AlipayResultAPIView(APIView):
"""
支付宝支付结果的通知处理
"""
def get(self,request):
# for rest_framework users
data = request.query_params.dict()
signature = data.pop("sign")
# 创建支付宝的sdk对象
alipay = AliPay(
appid= settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug = settings.ALIAPY_CONFIG["debug"], # 默认False
)
# verification
success = alipay.verify(data, signature)
if success:
# 修改订单状态
out_trade_no = data.get("out_trade_no")
try:
order = Order.objects.get(order_number=out_trade_no, order_status=0)
except Order.DoesNotExist:
return Response({"message": "对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
# 记录事务的回滚点
save_id = transaction.savepoint()
order.order_status = 1
order.pay_time = datetime.now()
order.save()
# 如果订单中使用了优惠券,则优惠券的使用状态要调整
if order.coupon > 0:
user_coupon_id = order.coupon
try:
user_coupon = UserCoupon.objects.get(pk=user_coupon_id,is_use=False)
user_coupon.is_use = True
user_coupon.save()
except UserCoupon.DoesNotExist:
log.error("生成订单支付结果有误!优惠券发生异常!")
transaction.savepoint_rollback(save_id)
# 如果用户使用了积分,则扣除相应积分
if order.credit > 0:
user = User.objects.get(pk=order.user_id)
user.credit = user.credit - order.credit
if user.credit > 0:
user.save()
else:
log.error("生成订单支付结果有误!积分计算有误!")
transaction.savepoint_rollback(save_id)
# 记录用户购买商品的记录信息
order_course = order.order_courses.all()
for item in order_course:
# 获取本次购买课程的有效期选项
try:
"""有效期选项"""
course_expire = CourseExpire.objects.get(expire_time=item.expire, course=item.course)
expire = course_expire.expire_time
timer = expire * 24 * 60 * 60
out_timestamp = order.pay_time.timestamp() + timer
# 把数值时间戳转变成日期对象
out_time = datetime.fromtimestamp(out_timestamp)
except CourseExpire.DoesNotExist:
"""永久有效,默认过期时间200年后"""
out_time = "2199-01-01 00:00:00"
"""
判断之前当前用户是否购买过同一商品,如果购买了同一商品,则在前面的过期时间基础上增加时间
过期时间,也需要判断,如果现在已经过期了,则购买完课程以后的过期时间 = 现在 + 有效期
如果现在没有过期,则购买完课程以后的过期时间 = 过期时间 + 有效期
购买完成,我们扣除了积分,但是我们也要针对本次消费的积分进行积分流水记录! Credit
"""
UserCourse.objects.create(
user=user,
course=item.course,
trade_no=data.get("trade_no"),
buy_type=1,
pay_time=order.pay_time,
out_time=out_time,
orders=0,
)
data = {
"order_number": order.order_number,
"pay_time": order.pay_time,
"real_price": order.real_price,
}
return Response(data)
else:
return Response({"message":"支付失败!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def post(self,request):
return Response({"message":"ok"})
payment/urls.py
,路由代码:
from django.urls import path, re_path
from . import views
urlpatterns = [
re_path(r'(?P<order_number>\d+)/alipay/', views.AliapyAPIView.as_view() ),
path(r'alipay/result/', views.AlipayResultAPIView.as_view() ),
]
客户端转发支付宝平台返回的同步通知结果,代码:
Success.vue
<template>
<div class="success">
<Header/>
<div class="main">
<div class="title">
<!-- <img src="../../static/images/right.svg" alt="">-->
<div class="success-tips">
<p class="tips1">您已成功购买 1 门课程!</p>
<p class="tips2">你还可以加入QQ群 <span>747556033</span> 学习交流</p>
</div>
</div>
<div class="order-info">
<p class="info1"><b>付款时间:</b><span>2019/04/02 10:27</span></p>
<p class="info2"><b>付款金额:</b><span >0</span></p>
<p class="info3"><b>课程信息:</b><span><span>《Pycharm使用秘籍》</span></span></p>
</div>
<div class="wechat-code">
<!-- <img src="../../static/image/server.cf99f78.png" alt="" class="er">-->
<!-- <p><img src="../../static/image/tan.svg" alt="">重要!微信扫码关注获得学习通知&课程更新提醒!否则将严重影响学习进度和课程体验!</p>-->
</div>
<div class="study">
<span>立即学习</span>
</div>
</div>
<Footer/>
</div>
</template>
<script>
import Header from "./common/Header"
import Footer from "./common/Footer"
export default{
name:"Success",
data(){
return {
current_page:0,
};
},
created(){
// 把地址栏上面的支付结果,转发给后端
this.get_result();
},
methods:{
get_result(){
this.$axios.get(`${this.$settings.Host}/payments/alipay/result/`+ location.search).then(response=>{
console.log(response.data);
}).catch(error=>{
console.log(error.response);
});
}
},
components:{
Header,
Footer,
}
}
</script>
接受异步支付结果
from rest_framework.views import APIView
from alipay import AliPay
from django.conf import settings
from orders.models import Order
from rest_framework.response import Response
from rest_framework import status
from datetime import datetime
from coupon.models import UserCoupon
from django.db import transaction
from users.models import UserCourse
from courses.models import CourseExpire
from users.models import User
import logging
log = logging.getLogger("django")
class AliapyAPIView(APIView):
def post(self,request,order_number):
"""生成支付宝支付链接的地址"""
# 接受订单信息
try:
# order_status=0 表示未支付宝
order = Order.objects.get(order_number=order_number,order_status=0)
except Order.DoesNotExist:
return Response({"message":"对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
# 创建支付宝的sdk对象
alipay = AliPay(
appid= settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug = settings.ALIAPY_CONFIG["debug"], # 默认False
)
# 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order.order_number, # 订单号
total_amount=float(order.real_price), # 订单总金额[单位:元]
subject=order.order_title, # 订单标题
return_url=settings.ALIAPY_CONFIG["return_url"], # 同步通知地址
notify_url=settings.ALIAPY_CONFIG["notify_url"], # 异步通知地址
)
pay_url = settings.ALIAPY_CONFIG["gateway_url"] + order_string
return Response({"pay_url":pay_url})
class AlipayResultAPIView(APIView):
"""
支付宝支付结果的通知处理
"""
def get(self,request):
# for rest_framework users
data = request.query_params.dict()
# 修改订单状态和购买记录,消费记录
return self.result(data)
def post(self,request):
"""处理异步通知结果
1. 线下开发是不起作用的,因为外网的支付宝服务器无法访问我们的局域网地址
2. 如果使用了代理服务器,通过代理方式提供对外访问时,异步通知会在转发请求的过程中存在丢失数据的可能
"""
data = request.data.dict()
return self.result(data)
def result(self,data):
signature = data.pop("sign")
# 创建支付宝的sdk对象
alipay = AliPay(
appid=settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug=settings.ALIAPY_CONFIG["debug"], # 默认False
)
# verification
success = alipay.verify(data, signature)
if success:
# 修改订单状态
out_trade_no = data.get("out_trade_no")
try:
order = Order.objects.get(order_number=out_trade_no, order_status=0)
except Order.DoesNotExist:
return Response({"message": "对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
# 记录事务的回滚点
save_id = transaction.savepoint()
order.order_status = 1
order.pay_time = datetime.now()
order.save()
# 如果订单中使用了优惠券,则优惠券的使用状态要调整
if order.coupon > 0:
user_coupon_id = order.coupon
try:
user_coupon = UserCoupon.objects.get(pk=user_coupon_id, is_use=False)
user_coupon.is_use = True
user_coupon.save()
except UserCoupon.DoesNotExist:
log.error("生成订单支付结果有误!优惠券发生异常!")
transaction.savepoint_rollback(save_id)
user = User.objects.get(pk=order.user_id)
# 如果用户使用了积分,则扣除相应积分
if order.credit > 0:
user.credit = user.credit - order.credit
if user.credit > 0:
user.save()
else:
log.error("生成订单支付结果有误!积分计算有误!")
transaction.savepoint_rollback(save_id)
# 记录用户购买商品的记录信息
order_course = order.order_courses.all()
for item in order_course:
# 获取本次购买课程的有效期选项
try:
"""有效期选项"""
course_expire = CourseExpire.objects.get(expire_time=item.expire, course=item.course)
expire = course_expire.expire_time
timer = expire * 24 * 60 * 60
out_timestamp = order.pay_time.timestamp() + timer
# 把数值时间戳转变成日期对象
out_time = datetime.fromtimestamp(out_timestamp)
except CourseExpire.DoesNotExist:
"""永久有效,默认过期时间200年后"""
out_time = "2199-01-01 00:00:00"
"""
判断之前当前用户是否购买过同一商品,如果购买了同一商品,则在前面的过期时间基础上增加时间
过期时间,也需要判断,如果现在已经过期了,则购买完课程以后的过期时间 = 现在 + 有效期
如果现在没有过期,则购买完课程以后的过期时间 = 过期时间 + 有效期
购买完成,我们扣除了积分,但是我们也要针对本次消费的积分进行积分流水记录! Credit
"""
UserCourse.objects.create(
user=user,
course=item.course,
trade_no=data.get("trade_no"),
buy_type=1,
pay_time=order.pay_time,
out_time=out_time,
orders=0,
)
data = {
"order_number": order.order_number,
"pay_time": order.pay_time,
"real_price": order.real_price,
}
return Response(data)
else:
return Response({"message": "支付失败!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
支付成功页面展示当前订单信息
api后端视图增加返回数据信息包括课程信息:
from rest_framework.views import APIView
from alipay import AliPay
from django.conf import settings
from orders.models import Order
from rest_framework.response import Response
from rest_framework import status
from datetime import datetime
from coupon.models import UserCoupon
from django.db import transaction
from users.models import UserCourse
from courses.models import CourseExpire
from users.models import User
import logging
log = logging.getLogger("django")
class AliapyAPIView(APIView):
def post(self,request,order_number):
"""生成支付宝支付链接的地址"""
# 接受订单信息
try:
# order_status=0 表示未支付宝
order = Order.objects.get(order_number=order_number,order_status=0)
except Order.DoesNotExist:
return Response({"message":"对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
# 创建支付宝的sdk对象
alipay = AliPay(
appid= settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug = settings.ALIAPY_CONFIG["debug"], # 默认False
)
# 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=order.order_number, # 订单号
total_amount=float(order.real_price), # 订单总金额[单位:元]
subject=order.order_title, # 订单标题
return_url=settings.ALIAPY_CONFIG["return_url"], # 同步通知地址
notify_url=settings.ALIAPY_CONFIG["notify_url"], # 异步通知地址
)
pay_url = settings.ALIAPY_CONFIG["gateway_url"] + order_string
return Response({"pay_url":pay_url})
class AlipayResultAPIView(APIView):
"""
支付宝支付结果的通知处理
"""
def get(self,request):
# for rest_framework users
data = request.query_params.dict()
# 修改订单状态和购买记录,消费记录
return self.result(data)
def post(self,request):
"""处理异步通知结果
1. 线下开发是不起作用的,因为外网的支付宝服务器无法访问我们的局域网地址
2. 如果使用了代理服务器,通过代理方式提供对外访问时,异步通知会在转发请求的过程中存在丢失数据的可能
"""
data = request.data.dict()
return self.result(data)
def result(self,data):
signature = data.pop("sign")
# 创建支付宝的sdk对象
alipay = AliPay(
appid=settings.ALIAPY_CONFIG["appid"],
app_notify_url=settings.ALIAPY_CONFIG["app_notify_url"], # 默认回调url
app_private_key_path=settings.ALIAPY_CONFIG["app_private_key_path"],
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_path=settings.ALIAPY_CONFIG["alipay_public_key_path"],
sign_type=settings.ALIAPY_CONFIG["sign_type"], # RSA 或者 RSA2
debug=settings.ALIAPY_CONFIG["debug"], # 默认False
)
# verification
success = alipay.verify(data, signature)
if success:
# 修改订单状态
out_trade_no = data.get("out_trade_no")
try:
order = Order.objects.get(order_number=out_trade_no, order_status=0)
except Order.DoesNotExist:
return Response({"message": "对不起当前订单不存在或者已经支付了!"}, status=status.HTTP_400_BAD_REQUEST)
with transaction.atomic():
# 记录事务的回滚点
save_id = transaction.savepoint()
order.order_status = 1
order.pay_time = datetime.now()
order.save()
# 如果订单中使用了优惠券,则优惠券的使用状态要调整
if order.coupon > 0:
user_coupon_id = order.coupon
try:
user_coupon = UserCoupon.objects.get(pk=user_coupon_id, is_use=False)
user_coupon.is_use = True
user_coupon.save()
except UserCoupon.DoesNotExist:
log.error("生成订单支付结果有误!优惠券发生异常!")
transaction.savepoint_rollback(save_id)
user = User.objects.get(pk=order.user_id)
# 如果用户使用了积分,则扣除相应积分
if order.credit > 0:
user.credit = user.credit - order.credit
if user.credit > 0:
user.save()
else:
log.error("生成订单支付结果有误!积分计算有误!")
transaction.savepoint_rollback(save_id)
# 记录用户购买商品的记录信息
order_course = order.order_courses.all()
course_list = []
for item in order_course:
# 获取本次购买课程的有效期选项
try:
"""有效期选项"""
course_expire = CourseExpire.objects.get(expire_time=item.expire, course=item.course)
expire = course_expire.expire_time
timer = expire * 24 * 60 * 60
out_timestamp = order.pay_time.timestamp() + timer
# 把数值时间戳转变成日期对象
out_time = datetime.fromtimestamp(out_timestamp)
except CourseExpire.DoesNotExist:
"""永久有效,默认过期时间200年后"""
out_time = "2199-01-01 00:00:00"
"""
判断之前当前用户是否购买过同一商品,如果购买了同一商品,则在前面的过期时间基础上增加时间
过期时间,也需要判断,如果现在已经过期了,则购买完课程以后的过期时间 = 现在 + 有效期
如果现在没有过期,则购买完课程以后的过期时间 = 过期时间 + 有效期
购买完成,我们扣除了积分,但是我们也要针对本次消费的积分进行积分流水记录! Credit
"""
UserCourse.objects.create(
user=user,
course=item.course,
trade_no=data.get("trade_no"),
buy_type=1,
pay_time=order.pay_time,
out_time=out_time,
orders=0,
)
course_list.append({
"id": item.course.id,
"name": item.course.name,
})
data = {
"order_number": order.order_number,
"pay_time": order.pay_time,
"real_price": order.real_price,
"course_list": course_list,
}
return Response(data)
else:
return Response({"message": "支付失败!"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
客户端中Success.vue
,代码:
<template>
<div class="success">
<Header/>
<div class="main">
<div class="title">
<!-- <img src="../../static/images/right.svg" alt="">-->
<div class="success-tips">
<p class="tips1">您已成功购买 {{result.course_list.length}} 门课程!</p>
<p class="tips2">你还可以加入QQ群 <span>747556033</span> 学习交流</p>
</div>
</div>
<div class="order-info">
<p class="info1"><b>付款时间:</b><span>{{result.pay_time|timeformat}}</span></p>
<p class="info2"><b>付款金额:</b><span >¥{{result.real_price}}</span></p>
<p class="info3"><b>课程信息:</b>
<span v-for="course in result.course_list">《{{course.name}}》</span>
</p>
</div>
<div class="wechat-code">
<!-- <img src="../../static/image/server.cf99f78.png" alt="" class="er">-->
<!-- <p><img src="../../static/image/tan.svg" alt="">重要!微信扫码关注获得学习通知&课程更新提醒!否则将严重影响学习进度和课程体验!</p>-->
</div>
<div class="study">
<span>立即学习</span>
</div>
</div>
<Footer/>
</div>
</template>
<script>
import Header from "./common/Header"
import Footer from "./common/Footer"
export default{
name:"Success",
data(){
return {
result:{},
};
},
created(){
// 把地址栏上面的支付结果,转发给后端
this.get_result();
},
filters:{
timeformat(time){
// 时间格式化
// 2019/04/02 10:27
let current_obj = new Date(time);
// 年份
let Y = current_obj.getFullYear();
// 月份
let m = current_obj.getMonth()+1;
m = m<10?"0"+m:m;
// 日期
let d = current_obj.getDate();
d = d<10?"0"+d:d;
// 小时
let H = current_obj.getHours();
H = H<10?"0"+H:H;
// 分钟
let i = current_obj.getMinutes();
i = i<10?"0"+i:i;
// 秒
let s = current_obj.getSeconds();
s = s<10?"0"+s:s;
return `${Y}/${m}/${d} ${H}:${i}`;
}
},
methods:{
get_result(){
this.$axios.get(`${this.$settings.Host}/payments/alipay/result/`+ location.search).then(response=>{
this.result = response.data
}).catch(error=>{
console.log(error.response);
});
}
},
components:{
Header,
Footer,
}
}
</script>
<style scoped>
.success{
padding-top: 80px;
}
.main{
height: 100%;
padding-top: 25px;
padding-bottom: 25px;
margin: 0 auto;
width: 1200px;
background: #fff;
}
.main .title{
display: flex;
-ms-flex-align: center;
align-items: center;
padding: 25px 40px;
border-bottom: 1px solid #f2f2f2;
}
.main .title .success-tips{
box-sizing: border-box;
}
.title img{
vertical-align: middle;
width: 60px;
height: 60px;
margin-right: 40px;
}
.title .success-tips{
box-sizing: border-box;
}
.title .tips1{
font-size: 22px;
color: #000;
}
.title .tips2{
font-size: 16px;
color: #4a4a4a;
letter-spacing: 0;
text-align: center;
margin-top: 10px;
}
.title .tips2 span{
color: #ec6730;
}
.order-info{
padding: 25px 48px;
padding-bottom: 15px;
border-bottom: 1px solid #f2f2f2;
}
.order-info p{
display: -ms-flexbox;
display: flex;
margin-bottom: 10px;
font-size: 16px;
}
.order-info p b{
font-weight: 400;
color: #9d9d9d;
white-space: nowrap;
}
.wechat-code{
display: flex;
-ms-flex-align: center;
align-items: center;
padding: 25px 40px;
border-bottom: 1px solid #f2f2f2;
}
.wechat-code>img{
width: 100px;
height: 100px;
margin-right: 15px;
}
.wechat-code p{
font-size: 14px;
color: #d0021b;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
}
.wechat-code p>img{
width: 16px;
height: 16px;
margin-right: 10px;
}
.study{
padding: 25px 40px;
}
.study span{
display: block;
width: 140px;
height: 42px;
text-align: center;
line-height: 42px;
cursor: pointer;
background: #ffc210;
border-radius: 6px;
font-size: 16px;
color: #fff;
}
</style>