暗黑模式
网站支付功能
文档介绍
本文档介绍了如何使用支付宝的网站支付功能,实现以下功能:
- 在网页端调用后台接口创建订单支付链接
- 用户通过扫码支付,后台监听接收支付宝支付成功的回调
- 以及网页端点击退款,后台接口实现退款
请详细阅读官方文档:
签约网站支付功能
登录商家平台
- 商家平台地址:https://b.alipay.com/
- 登录账号:
使用商家账号直接登录
或者使用个人账号登录,前提是该商家账号的管理员
个人账号登录商家平台
签约电脑网站支付




提醒
为了提高签约的成功率,最好先部署前端页面(后端不是必须的),让审核人员更直观的了解我们的网站是做什么的。
创建支付宝开放平台的网页应用
登录支付宝开放平台
- 支付宝开放平台地址:https://open.alipay.com/
注意
一定要用商家账号登录,因为网站支付等功能必须得是公司企业或个体工商户。
如果扫码登录的账号不是商家账号,请选择账号密码登录,然后输入商家邮箱/手机号和密码。


创建网页应用
- 在控制台中,点击开通网页应用
- 按照提示填写应用信息
提醒
一定要绑定企业/个体户账号,即签约了电脑网站支付功能的账号


开发配置
找到前面创建的应用

接口加签方式(密钥)








💥💥注意💥💥
- 密钥工具默认生成 PKCS8 格式的私钥,适用于 Java,如果要适用于 Python,一定要进行上面的第⑥步,把私钥转换为 PKCS1 格式。
- 应用私钥一定要保存好,一旦丢失将无法找回;应用公钥和支付宝公钥可以在应用开发设置中查看。
在代码中配置密钥
以 django
为例:
python
# .envs/.local/.django
ALIPAY_APP_ID=xxxxx # 开放平台应用 ID
ALIPAY_APP_PRIVATE_KEY=xxxxx # 开放平台应用私钥
ALIPAY_PUBLIC_KEY=xxxxx # 开放平台支付宝公钥
# .envs/.production/.django
ALIPAY_APP_ID=xxxxx # 开放平台应用 ID
ALIPAY_APP_PRIVATE_KEY=xxxxx # 开放平台应用私钥
ALIPAY_PUBLIC_KEY=xxxxx # 开放平台支付宝公钥
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
注意
django
默认会把.envs/.local/*
文件保存到仓库django
默认不会把.envs/.production/*
文件放进仓库
支付 API
查看可调用 API 和对应的文档

集成 alipay SDK
pip-requirements
# ./requirements/base.txt
alipay-sdk-python==3.7.369
1
2
2
python
# alipay.py
from alipay.aop.api.AlipayClientConfig import AlipayClientConfig
from alipay.aop.api.DefaultAlipayClient import DefaultAlipayClient
logger = logging.getLogger("alipay")
class AlipayClient:
_instance = None
def __new__(cls, *args, **kwargs):
"""单例模式"""
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
"""初始化 AlipayClient"""
sandbox_debug = False
alipay_client_config = AlipayClientConfig(sandbox_debug=sandbox_debug)
if settings.DEBUG and sandbox_debug:
alipay_client_config.server_url = (
"https://openapi-sandbox.dl.alipaydev.com/gateway.do" # 沙盒模式下,请使用这个支付网关
)
alipay_client_config.app_id = os.environ["ALIPAY_APP_ID"]
alipay_client_config.app_private_key = os.environ["ALIPAY_APP_PRIVATE_KEY"]
alipay_client_config.alipay_public_key = os.environ["ALIPAY_PUBLIC_KEY"]
alipay_client_config.timeout = 30
self.alipayClient = DefaultAlipayClient(alipay_client_config, logger)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
生成支付链接{#gen_pay_url}
接口说明
- 接口名称和文档地址:alipay.trade.page.pay(统一收单下单并支付页面接口)
- 接口使用场景
- 用户在我们的网页中下单某个商品
- 我们的后台调用此接口生成一个支付链接,并返回给前端网页
- 前端网页调转至此支付链接(可以内嵌到我们的网页里,也可以跳转到新的浏览器标签)
- 跳转后会自动展示收款二维码
- 用户扫码支付
- 支付成功后,支付宝会推送通知到我们的后台,参考这里
示例代码
python
# alipay.py
from alipay.aop.api.domain.AlipayTradePagePayModel import AlipayTradePagePayModel
from alipay.aop.api.request.AlipayTradePagePayRequest import AlipayTradePagePayRequest
class AlipayClient:
...
def pay_for_pc(self, course_id, user, return_url, comment) -> str:
"""
API name: alipay.trade.page.pay
API description: https://opendocs.alipay.com/open-v3/05w3qg?pathHash=e5f3724a#支付流程
API doc: https://opendocs.alipay.com/open-v3/2423fad5_alipay.trade.page.pay?scene=22&pathHash=b20c762a
API Debug: https://open.alipay.com/api/apiDebug
"""
if has_user_unprocess_or_finished_trade(user, course_id):
msg = "已经存在尚未处理的订单, 或已经成功支付了订单"
raise BadRequest(msg)
course = get_object_or_404(Course, id=course_id)
# 构造请求参数对象
trade_no = generate_trade_no("camp")
model = AlipayTradePagePayModel()
model.product_code = "FAST_INSTANT_TRADE_PAY"
model.out_trade_no = trade_no
model.timeout_express = "30m"
model.total_amount = float(course.price)
if settings.DEBUG:
model.total_amount = 0.01
model.subject = course.name
model.body = comment
model.qr_pay_mode = 2
request = AlipayTradePagePayRequest(biz_model=model)
if settings.DEBUG:
request.notify_url = "https://alipay.iflyit.top/api/courses/alipay_notify/"
else:
request.notify_url = "https://camp.teclub.cn/api/courses/alipay_notify/"
request.return_url = return_url
# 构建支付 URL
pay_url = self.alipayClient.page_execute(
request,
http_method="GET",
)
trade = Trade.objects.create(
trade_no=trade_no,
user=user,
course=course,
status="created",
price=course.price,
pay_url=pay_url,
)
trade.save() # 数据库保存订单信息
return pay_url # 返回支付链接给前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
异步通知
说明
- 什么是异步通知:对于电脑网站支付的交易,只有在用户支付完成之后,支付宝会根据 API 中商家传入的 notify_url,通过 POST 请求的形式将支付结果作为参数通知到商家系统。
- 电脑网站支付的异步通知说明文档
- 触发条件
- 只有
TRADE_SUCCESS
即用户扫码支付成功后,会触发回调 - 其他支付产品,例如手机支付,触发条件会不同,参考手机支付异步通知触发条件
- 只有
- 回调函数:
- 我们后台创建一个 POST 接口,不需要授权的
- 把这个步骤 的 notify_url 参数设为回调函数接口URL
- 当收到 POST 请求时
- 先验签,确保是支付宝官方,而不是恶意攻击
- 然后根据传给我们的
out_trade_no
TRADE_SUCCESS
等参数,处理我们自己的业务逻辑 - 最后,返回
success
代表处理成功;fail
代表失败,支付宝会再次调用我们的接口
示例代码
python
# views.py
class CourseViewSet(viewsets.ModelViewSet):
queryset = Course.objects.all()
serializer_class = CourseSerializer
@action(
detail=False,
methods=["post"],
permission_classes=[AllowAny],
)
def alipay_notify(self, request: Request):
pass_sign = verify_sign(request.data)
if pass_sign and verify_trade(request.data):
return Response("success", status=status.HTTP_200_OK)
return Response("fail", status=status.HTTP_400_BAD_REQUEST)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python
# alipay.py
from alipay.aop.api.util import SignatureUtils
def verify_sign(data: QueryDict):
data = data.copy()
sign = data.get("sign")
data.pop("sign") # 去除不用验签的字段
data.pop("sign_type") # 去除不用验签的字段
sign_content = SignatureUtils.get_sign_content(data)
return SignatureUtils.verify_with_rsa(
os.environ["ALIPAY_PUBLIC_KEY"],
sign_content.encode(),
sign,
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python
# alipay.py
def verify_trade(data: QueryDict):
app_id = data.get("app_id")
if app_id != os.environ["ALIPAY_APP_ID"]:
return False
out_trade_no = data.get("out_trade_no")
trade = get_object_or_404(Trade, trade_no=out_trade_no)
notify_id = data.get("notify_id")
trade.notify_id = notify_id
trade_status = data.get("trade_status")
if trade_status == "TRADE_SUCCESS":
trade.status = "completed"
elif trade_status == "TRADE_FINISHED":
trade.status = "completed_finished"
elif trade_status == "TRADE_CLOSED":
gmt_refund = data.get("gmt_refund")
trade.status = "cancelled_refund" if gmt_refund else "cancelled_timeout"
trade.save()
return True
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
其他 API
- alipay.trade.query(统一收单交易查询):查询支付宝交易订单
- alipay.trade.fastpay.refund.query(统一收单交易退款查询):查询退款是否成功