支付宝支付成功后未触发异步回调,如何排查?
结论
Postman 能成功请求回调地址,只说明该地址在当前网络环境下可以接收请求。它不能证明支付宝也能访问这个地址,也不能证明下单时已将 notify_url 正确提交给支付宝。
排查时主要检查三件事:
- 下单请求是否真的传入了
notify_url,参数位置是否正确; - 支付宝服务器能否通过公网访问该地址;
- 请求是否被网关、WAF、反向代理或应用拦截,因而没有进入业务日志。
最常见的情况是只配置了同步跳转地址 return_url,却漏掉了 notify_url,或者误将 notify_url 放进了 biz_content。
先确认异步通知地址是否随下单请求提交
return_url 和 notify_url 的用途不同:
return_url:支付完成后,引导用户浏览器跳回商户页面;notify_url:支付宝服务器向商户服务器发送异步POST通知。
因此,浏览器能正常跳回,并不代表异步通知地址已经生效。
以 alipay.trade.page.pay 为例,notify_url 属于公共请求参数,不应放在 biz_content 中。使用 Java SDK 时,可以直接设置到请求对象:
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl("https://www.example.com/pay/return");
request.setNotifyUrl("https://www.example.com/pay/alipay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609130001",
"total_amount": "100.00",
"subject": "订单支付",
"product_code": "FAST_INSTANT_TRADE_PAY"
}
""");
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
如果请求由自行拼接的参数或二次封装的 SDK 生成,应记录最终提交给支付宝网关的参数,并确认其中确实包含:
notify_url=https://www.example.com/pay/alipay/notify
不要只看配置文件。即使配置中有这个值,也可能因为没有传入请求、环境变量未加载,或者 URL 被后续代码覆盖,导致支付宝根本不知道该向哪里发送通知。
确认订单进入了可通知的交易状态
异步通知由交易状态变化触发。应通过商户订单号或支付宝交易号查询交易结果,不能只根据前端显示的”支付成功”页面判断。
可以调用:
alipay.trade.query
重点查看:
trade_status
常见状态包括:
WAIT_BUYER_PAY
TRADE_SUCCESS
TRADE_FINISHED
TRADE_CLOSED
如果订单仍处于 WAIT_BUYER_PAY,或者查询的订单与页面展示的并非同一笔,就不应继续按照”成功订单未回调”的方向排查。
还要确认支付请求、交易查询和商户后台查看的是同一个环境、同一个 app_id。正式环境与沙箱环境混用时,记录也无法对应。
从公网检查回调地址
支付宝服务器通过公网访问回调地址。以下情况都可能使请求无法到达应用:
- 地址是
localhost、127.0.0.1、内网 IP,或者域名只能在公司网络中访问; - 域名 DNS 解析异常;
- HTTPS 证书过期、证书链不完整或 TLS 配置不兼容;
- 防火墙、WAF、安全组或 CDN 拦截支付宝请求;
- 使用了未向公网开放的端口;
- 回调地址发生重定向;
- 路由只允许特定来源、请求头或 Content-Type;
- IPv4、IPv6 或多个节点的解析结果不一致。
建议离开商户网络后再测试:
curl -i -X POST 'https://www.example.com/pay/alipay/notify' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=TEST_ORDER&trade_status=TRADE_SUCCESS'
测试时应同时查看各层日志:
- CDN 或 WAF 访问日志;
- 负载均衡日志;
- Nginx、Apache 等反向代理日志;
- 应用入口日志;
- 业务处理日志。
如果只有业务日志,没有网关层日志,很容易把”请求被网关拒绝”误判成”支付宝没有发送请求”。
浏览器或 Postman 还可能使用本机代理、VPN、内网 DNS、Hosts 配置或已有的登录状态。它们能访问的地址,支付宝服务器不一定能访问。
检查接口是否接受支付宝的请求格式
支付宝通常通过 POST 发送异步通知,并以表单形式提交通知参数。回调接口不能只接受 JSON。
Spring Boot 可以按下面的方式接收原始参数:
@PostMapping(
value = "/pay/alipay/notify",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public ResponseEntity<String> notify(
HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, String.join(",", values));
});
// 记录必要参数后进行验签、订单校验和幂等处理
return ResponseEntity.ok("success");
}
如果项目启用了以下机制,需要针对回调接口作相应处理:
- 登录认证;
- Session 校验;
- CSRF 防护;
- API Token 校验;
- 统一 JSON 请求体校验;
- Referer 或 User-Agent 限制;
- IP 白名单;
- 全局异常拦截。
例如,Spring Security 可能把回调请求重定向到登录页,CSRF 过滤器也可能直接返回 403。此时请求已经到达服务器,但未必能进入控制器。
正确处理验签和响应
收到通知后,应使用支付宝公钥验证签名,并核对订单信息。即使验签失败,也不要完全跳过原始请求记录,否则后续很难判断究竟是”没有收到回调”,还是”收到回调但处理失败”。
Java 示例:
@PostMapping("/pay/alipay/notify")
public ResponseEntity<String> notify(HttpServletRequest request) throws Exception {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, String.join(",", values));
});
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return ResponseEntity.ok("failure");
}
String appId = params.get("app_id");
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
String totalAmount = params.get("total_amount");
if (!expectedAppId.equals(appId)) {
return ResponseEntity.ok("failure");
}
// 根据 outTradeNo 查询本地订单。
// 校验订单金额、商户身份及订单状态,并通过唯一约束实现幂等更新。
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 更新订单。重复通知时不得重复发货或重复记账。
}
return ResponseEntity.ok("success");
}
处理成功后,响应正文应为以下纯文本:
success
不要返回 JSON、HTML 页面或附带说明的字符串,例如:
{"success": true}
如果响应内容不符合要求、响应超时,或者返回了非成功 HTTP 状态,支付宝可能按其通知机制重试。具体的重试规则应以当前接入产品的官方文档为准。
建议增加入口级日志
入口日志应在验签和业务判断之前记录,至少保留以下内容:
- 请求时间;
- 请求方法和路径;
- HTTP 状态码;
app_id;out_trade_no;trade_no;trade_status;- 验签结果;
- 业务处理结果;
- 请求耗时。
不要完整记录敏感信息,也不要将私钥、Token 或全部支付参数长期写入普通日志。
可以在 Nginx 层为该路径单独记录日志:
location = /pay/alipay/notify {
access_log /var/log/nginx/alipay_notify_access.log combined;
proxy_pass http://payment_backend;
}
如果 Nginx 日志中没有请求,应继续检查 CDN、WAF、负载均衡和 DNS。如果 Nginx 有记录而应用没有,则检查反向代理路由、请求大小限制、超时设置和应用过滤器。
推荐的排查顺序
按以下顺序检查,通常能较快定位问题:
- 查询交易状态,确认该笔订单确实支付成功;
- 核对订单所属环境、
app_id、商户号和商户订单号; - 查看实际发往支付宝网关的请求,确认其中有正确的
notify_url; - 确认
notify_url没有被误放进biz_content; - 从外部网络检查域名、证书、端口和回调路径;
- 查看 CDN、WAF、负载均衡和 Web 服务器的访问日志;
- 检查登录认证、CSRF、请求格式限制和 IP 白名单;
- 确认接口可以接收表单格式的
POST请求; - 检查验签参数、公钥、字符集和签名类型;
- 确保处理成功后快速返回纯文本
success。
如果支付宝侧完全没有这笔通知的记录,服务器所有入口日志中也找不到请求,应先回到下单请求,确认 notify_url 是否真的传入。Postman 请求成功不能替代这项检查。
备注:内容仅供参考。
