luffy之支付宝使用
复习
#1 课程列表页面
#2 课程详情页面---》vue-video-player
#3 视频托管(文件)
-直接放到项目中
-公司自己搭建文件服务器(fastdfs,ceph,minio) 小而量大的问题
-第三方存储:阿里oss,七牛云存储(sdk)
-上传文件:js--》传到存储--》返回地址---》带着地址访问咱们后端接口,把地址存入数据库
# 支付:
-支付宝:商户号---》营业执照---》沙箱环境
-文档---》没有营业执照申请商户号
-微信:商户号---》营业执照
-银联:sdk
1 支付宝支付测试
# 支付宝支付流程
1.前端页面点击支付按钮
2.向后端提交下单请求
3.后端判断是否能下单,钱数对不对等
4.后台生成一个订单信息,存在订单表(待支付状态)
5.后端生成一个支付链接地址给前端
6.前端取出支付链接地址并跳转到此页面(这个地址是支付宝支付链接地址)
7.手机扫码或者登陆账号开始支付
8.支付成功后 支付宝会发两个回调地址一个给后端一个给前端
9.给后端地址是post请求(8次),通知后端已经支付成功,后端修改订单表为已支付,并回复给支付宝已收到
10.给钱端地址是get请求,前端会去订单表查询是否支付成功
# 以post回调作为修改订单的基准,如果post请求,返回没反应或者回复的内容不对,24小时内发8次,如果都没收到,一般不可能崩溃24小时
# 使用沙箱环境(支付宝支付测试使用) 官方没有pythn的sdk
-商户号
-用户号
# 所以使用第三方支付宝sdk
Python支付宝开源框架:https://github.com/fzlee/alipay
使用 pip install python-alipay-sdk
# 非对称加密
-公钥,私钥:
公钥只能加密
公钥可可以给任何人
私钥只有自己有
-加密用公钥加密,解密用私钥解密
# 对称加密:加密和解密的密码一样,
# 生成公钥私钥
-在本地使用命令生成
-支付网站,软件生成:https://docs.open.alipay.com/291/105971
-支付宝支付的话,仅有公钥私钥不行,还需要支付宝公钥---》把咱们的公钥填入---》生成支付宝公钥
#测试代码
from alipay import AliPay
# 应用私钥
APP_PRIVATE_KEY_STRING = open('./pri').read()
# 支付宝公钥
ALIPAY_PUBLIC_KEY_STRING = open('./pub').read()
# 支付对象
pay = AliPay(
appid='2021000119627806',
app_notify_url=None,
app_private_key_string=APP_PRIVATE_KEY_STRING,
alipay_public_key_string=ALIPAY_PUBLIC_KEY_STRING,
sign_type='RSA2',
debug=True
)
res = pay.api_alipay_trade_page_pay(
out_trade_no='123456', # 订单号
total_amount=float(99), # 只有生成支付宝链接时,不能用Decimal
subject='野生奥特曼', # 订单标题
return_url='http://127.0.0.1:8080/home', # get
notify_url='http://127.0.0.1:8080/home', # post
)
payurl = 'https://openapi.alipaydev.com/gateway.do?' + res
print(payurl)
#架构
libs
├── iPay # aliapy二次封装包
│ ├── __init__.py # 包文件
│ ├── pem # 公钥私钥文件夹
│ │ ├── alipay_public_key.pem # 支付宝公钥文件
│ │ ├── app_private_key.pem # 应用私钥文件
│ ├── pay.py # 支付文件
└── └── settings.py # 应用配置
2 支付宝支付二次封装 文件目录如下
2.1 pay.py
from alipay import AliPay
from . import settings
# 支付对象
pay = AliPay(
appid=settings.APP_ID,
app_notify_url=None,
app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
sign_type=settings.SIGN,
debug=settings.DEBUG
)
# 支付网关
gateway = settings.GATEWAY
# 这个方法可写可不写 看怎么调,这里不写了 外面想使用pay.api_alipay_trade_page_pay这个方法就可以
# def go_pay():
# res=pay.api_alipay_trade_page_pay(xx)
# return gateway+"?"+res
2.2 settings.py
import os
# 应用私钥
APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'app_private_key.pem')).read()
# 支付宝公钥
ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'alipay_public_key.pem')).read()
# 应用ID
APP_ID = '2021000119627806'
# 加密方式
SIGN = 'RSA2'
# 是否是支付宝测试环境(沙箱环境),如果采用真是支付宝环境,配置False
DEBUG = True
# 支付网关
GATEWAY = 'https://openapi.alipaydev.com/gateway.do' if DEBUG else 'https://openapi.alipay.com/gateway.do'
3 后台支付接口
# 记得注册app
# 登录认证()jwt认证
# 前端传入参数:商品总价格,登录用户(已经登录了,不需要携带了),courses:[1,2,3] 课程id
金额也传过来,后端会判断一下
{total_amount:99,courses:[1,2,3]}
# 后端:
-存单个Order表,顺带存Orderdetail表----》重写create
-所有判断逻辑写到序列化类中
-# 订单金额校验 判断传过来的金额和根据买的课程进行对比
-# 生成订单号 ---》uuid
-# 获取支付人 ----》request对象中,要存到表里,要有支付人
-# 获取支付链接--->封装好的支付宝支付
-# 入库(两个表)的信息准备
-# 入库----》重写create
视图类
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from .models import Order
from utils.response import APIResponse
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
from .serializer import PaySerializer
from utils.logging import logger
class OrderView(GenericViewSet, CreateModelMixin):
authentication_classes = [JSONWebTokenAuthentication, ]
permission_classes = [IsAuthenticated]
serializer_class = PaySerializer
queryset = Order.objects.all()
def create(self, request, *args, **kwargs):
try:
ser = self.get_serializer(data=request.data, context={'request': request})
ser.is_valid(raise_exception=True)
ser.save()
pay_url = ser.context.get('pay_url')
except Exception as e:
raise e
return APIResponse(pay_url=pay_url)
序列化类
from .models import Order, OrderDetail
from course.models import Course
from rest_framework import serializers
from rest_framework.validators import ValidationError
from libs.apay import pay,gateway
from django.conf import settings
class PaySerializer(serializers.ModelSerializer):
# courses=serializers.ListField() # 不用这个
# 如果这个 courses:1--->courses:id为1的这个课程 many=False
# 如果 courses:[1,2,3]---->courses:[对象1,对象2,对象3] q
# queryset存放的是对象
courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
class Meta:
model = Order
fields = ['total_amount', 'courses']
def validate(self, attrs):
# attrs 中有:total_amount,courses是对象列表
# 订单金额校验(校验传入的价格跟我们计算的价格是否一致)
self._check_price(attrs)
# 生成订单号 ---》uuid
trade_no = self._get_trade_no()
# 获取支付人 ----》request对象中
user=self._get_user()
# 获取支付链接--->封装好的支付宝支付
pay_url=self.get_pay_url(attrs,trade_no)
# 入库(两个表)的信息准备
attrs['subject']='路飞线上课程'
attrs['out_trade_no']=trade_no
attrs['user']=user
self.context['pay_url']=pay_url
return attrs
def _check_price(self, attrs):
total_amount = attrs.get('total_amount')
courses = attrs.get('courses')
real_total_amount = 0
for course in courses:
real_total_amount += course.price
if not total_amount == real_total_amount:
raise ValidationError('价格不一致')
def _get_trade_no(self):
import uuid
trade_no = str(uuid.uuid4()).replace('-', '')
return trade_no
def _get_user(self):
user=self.context.get('request').user
return user
def get_pay_url(self,attrs,trade_no):
'''
out_trade_no='123456', # 订单号
total_amount=float(99.99), # 只有生成支付宝链接时,不能用Decimal
subject='精品内衣',
return_url='http://127.0.0.1:8080/home',
notify_url='http://127.0.0.1:8080/home',
'''
res=pay.api_alipay_trade_page_pay(
out_trade_no=trade_no,
total_amount=float(attrs.get('total_amount')),
subject='路飞线上课程',
return_url=settings.RETURN_URL, # 放到配置文件 前端回调就是个前端页面,支付宝回调到支付成功页面
notify_url=settings.NOTIFY_URL, # 后端回调 接收支付宝的post回调,在这里面修改订单状态
)
return gateway+res
def create(self, validated_data):
# validated_data: [subject, out_trade_no, user courses total_amount]
courses = validated_data.pop('courses')
order=Order.objects.create(**validated_data)
# 存订单详情表
for course in courses:
OrderDetail.objects.create(course=course,order=order,price=course.price,real_price=course.price)
return order
路由
router.register('pay', views.OrderView, 'pay')
4 前端支付功能
go_pay(total_amount, course_id) {
// cookie中有token值,就是登录了
let token = this.$cookies.get('token')
if (token) {
this.$axios({
url: this.$settings.base_url + 'order/pay/',
method: 'post',
headers: {
Authorization: 'jwt ' + token,
},
data: {
'total_amount': total_amount,
'courses': [course_id,]
}
}).then(res => {
if (res.data.code == 100) {
// 跳转到支付页面
location.href = res.data.pay_url
}
})
// this.$axios.post(
// this.$settings.base_url + 'order/pay/',
// {
// 'total_amount': total_amount,
// 'courses': [course_id,]
// }, {
// headers: {
// 'Authorization': 'jwt ' + token
// },
// }
// ).then(res => {
// if (res.data.code == 100) {
// // 跳转到支付页面
// location.href = res.data.pay_url
// }
// })
} else {
this.$message({
message: "您没有登录,请先登录",
})
}
}
5 支付成功回调接口
# 正常只需要一个post回调给支付宝用(登录认证?支付宝要验证签名---》才信任,才改订单状态)
# 咱们写了一个get回调,给前端用,做双重验证保险一些
# 内网穿透:https://zhuanlan.zhihu.com/p/370483324
# 前端代码编译
npm run build
from rest_framework.views import APIView
from .models import Order
class PaySuccessView(APIView):
def get(self,request): # 自己用的
try:
out_trade_no=request.query_params.get('out_trade_no')
# 根据订单号,查订单的状态
Order.objects.get(out_trade_no=out_trade_no,order_status=1)
except Exception as e:
raise e
return APIResponse()
# 给支付宝用---》回调回来数据格式
def post(self,request):
# django :requset.POST request.GET --->QueryDict的对象--》不允许删除数据
try:
result_data = request.data.dict() # request.data 是post的数据,dict()作用是复制了一份数据
out_trade_no = result_data.get('out_trade_no') # 订单号
signature = result_data.pop('sign') # 签名
from libs import apay
# 验证签名,只有这个正确,才能修改订单状态--》防止恶意发送post请求给我们
result = apay.pay.verify(result_data, signature)
if result and result_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
# 完成订单修改:订单状态、流水号、支付时间
models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1)
# 完成日志记录
logger.warning('%s订单支付成功' % out_trade_no)
return Response('success') # 固定的
else:
logger.error('%s订单支付失败' % out_trade_no)
except:
pass
return Response('failed')
6 git上传前端需要转成静态页面
执行:npm run build
会在目录下生成一个dist文件夹,里面的就是静态页面