支付宝支付
支付宝支付
支付宝支付流程
-我们自己的网站:点击购买按钮---》向我们后端发送请求---》携带购买商品信息---》生成订单,入库,订单是未支付状态-----》生成支付宝支付链接----》返回给前端
-前端拿到支付链接---》get请求打开----》咱们的前端就来到了支付宝的页面--》用户掏出手机扫描支付---》付款完成----》支付宝收到了钱----》get回调(咱们配置回调地址)----》跳回我们自己的网页---》支付宝还会发送post请求给我们后端----》我们要验签,通过后,把订单状态改为已支付状态
目前基于沙箱环境测试
python使用沙箱环境模拟支付
基于sdk
-pip install python-alipay-sdk --upgrade
封装支付宝支付
libs
├── iPay # aliapy二次封装包
│ ├── __init__.py # 包文件
│ ├── pem # 公钥私钥文件夹
│ │ ├── alipay_public_key.pem # 支付宝公钥文件
│ │ ├── app_private_key.pem # 应用私钥文件
│ ├── pay.py # 支付文件
└── └── settings.py # 应用配置
alipay_public_key.pem
-----BEGIN PUBLIC KEY-----
拿应用公钥跟支付宝换来的支付宝公钥
-----END PUBLIC KEY-----
app_private_key.pem
-----BEGIN RSA PRIVATE KEY-----
通过支付宝公钥私钥签发软件签发的应用私钥
-----END RSA PRIVATE KEY-----
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 = 应用ID
# 加密方式
SIGN = 'RSA2'
# 是否是支付宝测试环境(沙箱环境),如果采用真是支付宝环境,配置False
DEBUG = True
# 支付网关
GATEWAY = 'https://openapi-sandbox.dl.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'
pay.py
from alipay import AliPay
from alipay.utils import AliPayConfig
from . import settings
alipay = AliPay(
appid=settings.APP_ID,
app_notify_url=None, # 默认回调 url
app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
sign_type=settings.SIGN, # RSA 或者 RSA2
debug=settings.DEBUG, # 默认 False
verbose=settings.DEBUG, # 输出调试数据
config=AliPayConfig(timeout=15) # 可选,请求超时时间
)
init.py
from .pay import alipay
from .settings import GATEWAY
补充:在自己项目的配置文件中配置支付宝回调接口:settings.py | dev.py
# 上线后必须换成公网地址
# 后台基URL
BASE_URL = 'http://127.0.0.1:8000'
# 前台基URL
LUFFY_URL = 'http://127.0.0.1:8080'
# 支付宝同步异步回调接口配置
# 后台异步回调接口
NOTIFY_URL = BASE_URL + "/order/success/"
# 前台同步回调接口,没有 / 结尾
RETURN_URL = LUFFY_URL + "/pay/success"
BACKEND_URL = "http://127.0.0.1:8000"
LUFFY_URL = "http://127.0.0.1:8080"
RETURN_URL = LUFFY_URL + "/order/success"
NOTIFY_URL = BACKEND_URL + "/api/v1/order/paysuccess/"
"""
1)支付接口(需要登录认证:是谁):前台提交商品等信息,得到支付链接
post方法
分析:支付宝回调
同步:get给前台 => 前台可以在收到支付宝同步get回调时,ajax异步在给消息同步给后台,也采用get,后台处理前台的get请求
异步:post给后台 => 后台直接处理支付宝的post请求
2)支付回调接口(不需要登录认证:哪个订单(订单信息中有非对称加密)、支付宝压根不可能有你的token):
get方法:处理前台来的同步回调(不一定能收得到,所有不能在该方法完成后台订单状态等信息操作)
post方法:处理支付宝来的异步回调
3)订单状态确认接口:随你前台任何时候来校验订单状态的接口
"""
简单支付案例
视图
class payView(GenericViewSet, CreateModelMixin):
# @action(methods=["post"],detail=False)
# def pay(self, request):
# res = alipay.api_alipay_trade_page_pay(subject="衣服", out_trade_no="sdfsfdsf", total_amount='9999')
# print(GATEWAY + res)
# return APIResponse(pay_url=GATEWAY + res)
queryset = Order.objects.all()
serializer_class = OrderSerializer
def create(self, request, *args, **kwargs):
print(request.data)
serializer = self.get_serializer(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
pay_url = serializer.context.get('pay_url')
return APIResponse(pay_url=pay_url)
序列化类
class OrderSerializer(serializers.ModelSerializer):
'''
1 取出所有id号,拿到数据
2 统计总价格,跟传入的total_amount做比较,如果一样,继续往后
3 获取购买人信息:登录后才能访问的接口 request.user
4 生成订单号 支付链接需要,存订单表需要
5 生成支付链接:支付宝支付生成,
6 生成订单记录,订单是待支付状态(order,order_detail)
7 返回前端支付链接
'''
courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), many=True)
class Meta:
model = Order
fields = ['courses', 'total_amount', 'subject']
# 5生成支付链接:支付宝支付生成,
def _order_url(self, subject, out_trade_no, total_amount):
res = alipay.api_alipay_trade_page_pay(subject=subject, out_trade_no=out_trade_no, total_amount=total_amount,
return_url=settings.RETURN_URL, # 前端的
notify_url=settings.NOTIFY_URL)
print(res)
self.context["pay_url"] =GATEWAY + res
# 4生成订单号支付链接需要,存订单表需要
def _order_id(self):
return str(uuid.uuid4())
# 3获取购买人信息:登录后才能访问的接口
def _user(self):
print(self.context.get("request"))
return self.context.get("request").user
def validate(self, attrs):
subject = attrs.get("subject")
out_trade_no = self._order_id()
total_amount = int(self._total_amount(attrs))
user=self._user()
print(self._order_url(subject=subject,out_trade_no=out_trade_no,total_amount=total_amount))
attrs["subject"] = subject
attrs["out_trade_no"] = out_trade_no
attrs["total_amount"] = total_amount
attrs["user"] = user
return attrs
# 2统计总价格,跟传入的total_amount做比较,如果一样,继续往后
def _total_amount(self, attrs):
courses = attrs.get("courses")
total_amount = attrs.get("total_amount")
common_total_amount = 0
for course in courses:
common_total_amount += course.price
if total_amount == common_total_amount:
return common_total_amount
raise APIException("价格有误")
def create(self, validated_data):
print(validated_data)
# validated_data:{subject,total_amount,user,out_trade_no,courses}
courses = validated_data.pop('courses')
order = Order.objects.create(**validated_data)
# 存订单详情表,存几条,取决于courses有几个
for course in courses:
OrderDetail.objects.create(order=order, course=course, price=course.price, real_price=course.price)
return order
前段回调
let params = location.search.substring(1); // 去除? => a=1&b=2
let items = params.length ? params.split('&') : []; // ['a=1', 'b=2']
//逐个将每一项添加到args对象中
for (let i = 0; i < items.length; i++) { // 第一次循环a=1,第二次b=2
let k_v = items[i].split('='); // ['a', '1']
//解码操作,因为查询字符串经过编码的
if (k_v.length >= 2) {
// url编码反解
let k = decodeURIComponent(k_v[0]);
this.result[k] = decodeURIComponent(k_v[1]);
// 没有url编码反解
// this.result[k_v[0]] = k_v[1];
}
}
// 解析后的结果
// console.log(this.result);
// 把地址栏上面的支付结果,再get请求转发给后端
this.$axios({
url: this.$settings.base_url + '/order/success/' + location.search,
method: 'get',
}).then(response => {
console.log(response.data);
}).catch(() => {
console.log('支付结果同步失败');
})
后端回调接口
class PaySuccess(APIView):
authentication_classes = []
def get(self, request): # 咱们用的
out_trade_no = request.query_params.get('out_trade_no')
order = Order.objects.filter(out_trade_no=out_trade_no, order_status=1).first()
if order: # 支付宝回调完, 订单状态改了
return APIResponse()
else:
return APIResponse(code=101, msg='暂未收到您的付款,请稍后刷新再试')
def post(self, request): # 给支付宝用的,项目需要上线后才能看到 内网中,无法回调成功【使用内网穿透】
try:
result_data = request.data.dict() # requset.data 是post提交的数据,如果是urlencoded格式,requset.data是QueryDict对象,方法dict()---》转成真正的字典
out_trade_no = result_data.get('out_trade_no')
signature = result_data.pop('sign')
# 验证签名的---》验签
result = alipay.alipay.verify(result_data, signature)
if result and result_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
# 完成订单修改:订单状态、流水号、支付时间
Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1)
# 完成日志记录
logging.warning('%s订单支付成功' % out_trade_no)
return Response('success') # 都是支付宝要求的
else:
logging.error('%s订单支付失败' % out_trade_no)
except:
pass
return Response('failed') # 都是支付宝要求的
def put(self, request): # 咱们用的
out_trade_no = request.query_params.get('out_trade_no')
order = Order.objects.filter(out_trade_no=out_trade_no)
if order:
order.update(order_status=1)
return APIResponse(mag="订单修改成功")