支付流程
- 前台将商品价格和商品名称发给后台
- 后台生成订单编号并结合商品信息、前台接口、后台接口生成支付宝的支付连接,将支付连接返回给前台
- 用户通过后台返回的支付连接进行支付(这一步的页面及支付请求操作的都是支付宝后台),支付成功后,支付宝将上一步设置的前台接口同步回调给用户,并异步访问后台接口.
补充:
第二步的 前台接口 就是支付宝支付成功跳转后的页面
注意:
支付宝同步调用前台接口,会将订单编号,流水号,支付时间返回给该接口
支付宝异步调用后台接口,后台修改订单状态,并给返回success
使用
创建应用
登录支付宝开发者平台,点击网页&移动应用,输入应用名等信息
这里我们用支付宝的沙箱环境模拟出一个应用
生成支付宝公钥
安装支付宝的支付开发工具: https://opendocs.alipay.com/open/291/105971
- 在开发工具中生成应用私钥和应用公钥
- 拿着应用公钥到沙盒应用中换取支付宝公钥
代码编写
安装支付宝开源框架: https://github.com/fzlee/alipay
pip install python-alipay-sdk --upgrade
普通使用:
from alipay import AliPay
# 应用私钥
app_private_key_string = """-----BEGIN RSA PRIVATE KEY-----
MIIEp......8X68tezc=
-----END RSA PRIVATE KEY-----"""
# 支付宝公钥
alipay_public_key_string = """-----BEGIN PUBLIC KEY-----
MIIBI......fYEmkDtYwIDAQAB
-----END PUBLIC KEY-----"""
alipay = AliPay(
appid="2016093000631831",
app_notify_url=None, # 默认不用设置
app_private_key_string=app_private_key_string,
# 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
alipay_public_key_string=alipay_public_key_string,
sign_type="RSA2", # RSA 或者 RSA2 (沙盒测试时用RSA2)
debug=True # 默认False
)
import time
if __name__ == '__main__':
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=str(time.time()), # 订单编号
total_amount=6.66, # 订单价格
subject='小红帽', # 商品名称
return_url="http://127.0.0.1:8080", # 支付成功后同步回调的项目前台页面
notify_url="https://example.com/notify" # 支付成功后异步回调的项目后台接口
)
# 支付连接 = 支付宝网关 + order_string
order_url = 'https://openapi.alipaydev.com/gateway.do?' + order_string
print(order_url) # 得到的是支付宝的支付连接
高级使用,二次封装
结构
libs
├── iPay # aliapy二次封装包
│ ├── __init__.py # 包文件
│ ├── keys # 密钥文件夹
│ │ ├── alipay_public_key.pem # 支付宝公钥
│ │ └── app_private_key.pem # 应用私钥
└── └── settings.py # 应用配置
- setting.py
import os
# 支付宝应用APPID
APP_ID = '2016093000631831'
# 默认异步回调的地址,通常设置None就行
APP_NOTIFY_URL = None
# 应用私钥文件路径
APP_PRIVATE_KEY_PATH = os.path.join(os.path.dirname(__file__), 'keys', 'app_private_key.pem')
# 支付宝公钥文件路径
ALIPAY_PUBLIC_KEY_PATH = os.path.join(os.path.dirname(__file__), 'keys', 'alipay_public_key.pem')
# 签名方式
SIGN_TYPE = 'RSA2'
# 是否是测试环境 - 是否是支付宝沙箱
DEBUG = True
# 支付连接(支付宝网关)
DEV_PAY_URL = 'https://openapi.alipaydev.com/gateway.do?' # 沙盒
PROD_PAY_URL = 'https://openapi.alipay.com/gateway.do?'
- alipay_public_key.pem
-----BEGIN PUBLIC KEY-----
支付宝公钥
-----END PUBLIC KEY-----
- app_private_key.pem
-----BEGIN RSA PRIVATE KEY-----
应用私钥
-----END RSA PRIVATE KEY-----
- __init__.py
from alipay import AliPay
from .settings import *
# 对外提供支付对象
alipay = AliPay(
appid=APP_ID, # 支付宝应用id
app_notify_url=APP_NOTIFY_URL, # 默认异步回调的地址,通常设置None就行
app_private_key_path=APP_PRIVATE_KEY_PATH, # 应用私钥文件路径
alipay_public_key_path=ALIPAY_PUBLIC_KEY_PATH, # 支付宝公钥文件路径
sign_type=SIGN_TYPE, # 签名方式
debug=DEBUG # 是否是测试环境
)
# 对外提供的支付链接(支付宝网关)前缀
pay_url = DEV_PAY_URL if DEBUG else PROD_PAY_URL
- test.py
from libs.iPay import alipay, pay_url
import time
if __name__ == '__main__':
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=str(time.time()),
total_amount=6.66,
subject='小红帽',
return_url="http://127.0.0.1:8080", # 同步回调接口
notify_url="https://example.com/notify" # 异步回调接口
)
order_url = pay_url + order_string
print(order_url)
案例
- dev.py
# 前后台base_url
UP_BASE_URL = 'http://127.0.0.1:8080'
END_BASE_URL = 'http://127.0.0.1:8000'
# alipay回调接口配置
# 上线后必须换成官网地址
# 同步回调的接口(get),前后台分离时一般设置前台页面url
RETURN_URL = UP_BASE_URL + '/pay/success'
# 异步回调的接口(post),一定设置为后台服务器接口
NOTIFY_URL = END_BASE_URL + '/order/success/'
- views.py
from django.shortcuts import render
# Create your views here.
from rest_framework.views import APIView
from libs.ipay import alipay, pay_url
import time
from . import models
from django.conf import settings
from utils.response import APIResponse
from utils.logging import logger
from rest_framework.response import Response
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated
# 支付接口需要登录认证
class PayAPIView(APIView):
authentication_classes = [JSONWebTokenAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
# 1)获取前台信息:商品、价格、支付方式
request_data = request.data
subject = request_data.get('subject')
total_amount = request_data.get('total_amount')
pay_type = request_data.get('pay_type')
if not (subject and total_amount and pay_type):
return APIResponse(2, '数据有误')
# 2)生成订单(订单号,订单表的订单记录)
out_trade_no = str(time.time())
user = request.user # 当前登录用户
try:
models.Order.objects.create(subject=subject, total_amount=total_amount, pay_type=pay_type, out_trade_no=out_trade_no, user=user)
except:
return APIResponse(1, '订单生成失败')
# 3)生成支付链接,并返回
order_string = alipay.api_alipay_trade_page_pay(
out_trade_no=out_trade_no,
total_amount=total_amount,
subject=subject,
return_url=settings.RETURN_URL,
notify_url=settings.NOTIFY_URL
)
order_url = pay_url + order_string
return APIResponse(order_url=order_url)
# 支付成功的回调不需要登录认证 - 支付宝回调不会携带jwt-token,但是支付回调参数需要自己做校验
class SuccessAPIView(APIView):
def patch(self, request, *args, **kwargs):
# request.query_params是QueryDict类型,不能调用pop方法
request_data = request.query_params.dict()
signature = request_data.pop("sign")
success = alipay.verify(request_data, signature)
if success: # 校验通过
print("通过")
# 一般不在该处修改订单状态
return APIResponse()
return APIResponse(1, '校验失败')
# 支付宝异步回调
def post(self, request, *args, **kwargs):
# 默认是QueryDict类型,不能使用pop方法
request_data = request.data.dict()
# 必须将 sign、sign_type(内部有安全处理) 从数据中取出,拿sign与剩下的数据进行校验
sign = request_data.pop('sign')
result = alipay.verify(request_data, sign)
# 异步回调:修改订单状态
if result and request_data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
out_trade_no = request_data.get('out_trade_no')
logger.critical('%s支付成功' % out_trade_no)
try:
order = models.Order.objects.get(out_trade_no=out_trade_no)
if order.order_status != 1:
order.order_status = 1
order.save()
return Response('success') # 必须返回success字符串,8次异步回调机制
except:
pass
return Response('failed')
- urls.py
from django.urls import path, re_path
from . import views
urlpatterns = [
# 支付接口 - 订单信息换支付链接
path('pay/', views.PayAPIView.as_view()),
# 支付成功结果 - 修改订单状态
path('success/', views.SuccessAPIView.as_view())
]