alipay-sdk-python中app pay支付成功但未收到回调通知
结论
app pay 支付成功却收不到异步通知,通常不是支付结果有问题,而是生成 App 支付订单时未正确设置 notify_url,或支付宝服务器无法访问该回调地址。
客户端在 App 支付完成后返回的结果不能代替服务端异步通知。需要检查实际提交给 alipay.trade.app.pay 的请求参数,确认其中包含正确的 notify_url。回调接口还要能够接收 POST 请求、验证支付宝签名,并在处理成功后返回纯文本 success。
重点排查项
1. 确认 App 支付订单设置了 notify_url
page pay 和 app pay 使用不同的接口。即使网页支付已经配置回调地址,App 支付也不会自动复用。
使用 alipay-sdk-python 时,可以在初始化客户端时设置默认通知地址:
from alipay import AliPay
alipay = AliPay(
appid="你的 AppID",
app_notify_url="https://example.com/alipay/notify",
app_private_key_string=app_private_key_string,
alipay_public_key_string=alipay_public_key_string,
sign_type="RSA2",
debug=False,
)
然后生成 App 支付订单:
order_string = alipay.api_alipay_trade_app_pay(
out_trade_no="IM2FAJUCT",
total_amount="0.01",
subject="订单标题",
)
如果当前 SDK 版本或封装方式允许在调用接口时单独传入 notify_url,也可以显式设置:
order_string = alipay.api_alipay_trade_app_pay(
out_trade_no="IM2FAJUCT",
total_amount="0.01",
subject="订单标题",
notify_url="https://example.com/alipay/notify",
)
不同 Python 支付宝 SDK 的方法签名可能不同。如果调用时报 unexpected keyword argument 'notify_url',应通过初始化参数 app_notify_url 进行配置。无论采用哪种方式,都要检查最终生成并提交的 App 支付请求,确认其中确实包含 notify_url。
return_url 用于浏览器同步跳转,不能代替 notify_url。
2. 确认回调地址可从公网访问
异步通知由支付宝服务器发起,因此回调地址需要满足以下条件:
- 可以通过公网域名访问;
- 建议使用有效的 HTTPS 证书;
- 不依赖登录状态、Cookie 或前端 Token;
- 不受内网、防火墙、安全组或错误的 IP 白名单配置影响;
- 路由允许
POST请求; - 不会被 CSRF 校验直接拦截;
- 不会发生异常的 HTTP 到 HTTPS 跳转;
- 接口能在合理时间内处理请求并返回 HTTP 200。
不要使用以下地址:
http://127.0.0.1:8000/alipay/notify
http://localhost/alipay/notify
http://192.168.x.x/alipay/notify
可以先从外网环境向回调地址发送一次普通 POST 请求,再查看应用日志、网关日志和 Web 服务器访问日志,确认请求能够到达。
3. 正确接收表单格式的回调数据
支付宝异步通知通常采用 application/x-www-form-urlencoded 格式,而不是 JSON。以 Flask 为例:
from flask import Flask, request, Response
app = Flask(__name__)
@app.post("/alipay/notify")
def alipay_notify():
callback_data = request.form.to_dict()
signature = callback_data.pop("sign", None)
callback_data.pop("sign_type", None)
if not signature:
return Response("failure", status=400, mimetype="text/plain")
if not alipay.verify(callback_data, signature):
return Response("failure", status=400, mimetype="text/plain")
# 验签成功后还需要核对订单信息
out_trade_no = callback_data.get("out_trade_no")
trade_status = callback_data.get("trade_status")
total_amount = callback_data.get("total_amount")
callback_app_id = callback_data.get("app_id")
if callback_app_id != "你的 AppID":
return Response("failure", status=400, mimetype="text/plain")
order = load_order(out_trade_no)
if order is None:
return Response("failure", status=400, mimetype="text/plain")
if total_amount != order.total_amount:
return Response("failure", status=400, mimetype="text/plain")
if trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
mark_order_paid_once(
order=order,
alipay_trade_no=callback_data.get("trade_no"),
)
return Response("success", mimetype="text/plain")
其中,load_order 和 mark_order_paid_once 需要由业务系统实现,分别用于查询订单和更新订单状态。
验签必须使用支付宝公钥,不能使用应用公钥或应用私钥。生产环境与沙箱环境的密钥、网关和 AppID 也不能混用。
4. 回调成功后必须返回纯文本 success
业务处理成功后,接口应直接返回:
success
不要返回 JSON:
{"status": "success"}
也不要返回 HTML 页面、重定向响应或带有额外字符的内容。如果支付宝没有收到符合要求的响应,可能会继续重试通知,但业务系统不能只依靠重试机制保证订单状态一致。
5. 检查网关和应用日志
可以按照请求链路逐层排查:
- 检查生成订单时记录的
notify_url。 - 检查 Nginx、负载均衡或 API 网关访问日志。
- 检查应用路由是否收到
POST请求。 - 检查请求是否被 WAF、CSRF、中间件或鉴权逻辑拦截。
- 检查验签失败日志。
- 检查业务处理是否抛出异常或触发数据库回滚。
- 检查最终响应正文是否严格为
success。
如果网关日志中完全没有支付宝请求,应优先确认 notify_url 是否提交成功,以及回调地址能否从公网访问。如果网关收到了请求,但应用没有收到,需要检查代理转发和路由配置。如果应用已经收到请求,订单状态却没有更新,则应检查验签、金额核对和业务事务处理。
对已支付订单进行主动查询
配置错误或网络问题可能导致业务系统没有处理异步通知,因此不能只依赖客户端提交的“支付成功”结果。可以使用 out_trade_no 主动查询交易状态:
result = alipay.api_alipay_trade_query(
out_trade_no="IM2FAJUCT"
)
if result.get("code") == "10000":
trade_status = result.get("trade_status")
if trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
# 核对 app_id、订单金额、商户身份等信息后,
# 再以幂等方式更新本地订单
pass
查询结果只能用于服务端补偿。更新订单前,仍要核对商户订单号、交易金额、AppID 和收款方身份等信息。
还可以增加定时补偿任务,对长时间处于“待支付”状态的订单调用交易查询接口,避免一次异步通知异常造成订单长期不一致。
注意事项
- App 客户端返回的支付结果可能被伪造,不能直接据此发货或开通服务。
- 回调处理必须具备幂等性。同一笔订单即使收到多次通知,也只能执行一次业务变更。
- 验签成功不代表订单一定可信,还要核对
app_id、out_trade_no、total_amount、seller_id等字段。 - 金额建议使用
Decimal或规范化后的字符串进行比较,避免浮点数精度问题。 - 回调接口应先完成必要校验和可靠落库,再返回
success。 - 不要在回调处理中执行耗时较长的任务。订单落库后,可以交给消息队列异步处理。
- 日志可以记录
out_trade_no、trade_no和trade_status,但不能记录应用私钥等敏感信息。 - 如果重新生成订单参数,客户端必须使用新的完整订单字符串,不能继续使用此前未包含
notify_url的缓存参数。
针对这个问题,应先检查 out_trade_no=IM2FAJUCT 对应的 App 支付请求是否实际携带了正确的 notify_url。如果没有,需要在 AliPay 初始化时设置 app_notify_url,重新生成订单字符串,再结合公网访问日志确认支付宝通知是否到达。
备注:内容仅供参考。
