支付宝沙箱支付突然收不到异步回调,如何排查?
结论
支付宝沙箱突然收不到异步通知,通常不是支付接口失效,而是下面某个环节出了问题:
- 本次支付请求没有传入有效的
notify_url。 - 回调地址无法从公网访问,比如内网穿透地址失效、域名解析异常或防火墙拦截。
- HTTPS 证书过期、证书链不完整,或者 TLS 配置不兼容。
- 支付宝已经发出通知,但接口响应超时,或者返回内容不是
success。 - 应用公钥、支付宝公钥、签名方式或沙箱应用配置发生了变化。
- 支付宝沙箱环境暂时异常。
排查时,先确认订单确实支付成功,再查看服务端访问日志。这样可以判断支付宝究竟是没有发送通知,还是已经发送,但服务端处理失败。
第一步:确认订单是否真的支付成功
异步通知只会在交易状态发生相应变化时触发。先使用沙箱买家账号完成付款,再调用 alipay.trade.query 查询订单状态。
需要关注以下结果:
TRADE_SUCCESS:交易支付成功。TRADE_FINISHED:交易完成。WAIT_BUYER_PAY:买家尚未付款。- 查询不到订单:检查
out_trade_no、沙箱网关和应用环境是否一致。
如果订单仍是 WAIT_BUYER_PAY,没有收到支付成功通知是正常的。
主动查询可以用来确认和补偿订单状态,但不能证明支付宝一定发送过异步通知。
第二步:检查实际请求中的 notify_url
不要只看配置文件,还要检查最终发送给支付宝的支付请求。
以 Java SDK 为例:
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl("https://example.com/pay/return");
request.setNotifyUrl("https://example.com/api/alipay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001",
"total_amount": "0.01",
"subject": "沙箱测试订单",
"product_code": "FAST_INSTANT_TRADE_PAY"
}
""");
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
检查时注意:
return_url是浏览器的同步跳转地址。notify_url是支付宝服务器发送异步通知的地址。- 用户关闭支付页面不会影响
notify_url。 - 只配置
return_url不会自动产生异步通知。 - 如果全局配置和请求对象都设置了地址,需要确认最终使用的是哪个值。
notify_url不要包含#fragment,也不能依赖浏览器 Cookie 或用户登录状态。
即使昨天和今天的代码完全相同,也要检查环境变量、配置中心、容器配置和实际部署版本是否发生了变化。
第三步:确认回调地址可以被公网访问
支付宝服务器无法访问以下地址:
http://localhost:8080/alipay/notify
http://127.0.0.1:8080/alipay/notify
http://192.168.1.10:8080/alipay/notify
使用内网穿透工具时,免费隧道地址可能会在重启后改变,也可能临时离线。这是出现”昨天正常,今天突然失效”的常见原因。
可以通过非本机网络测试回调地址,例如使用手机蜂窝网络访问,或者从公网服务器执行:
curl -i -X POST https://example.com/api/alipay/notify \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'test=1'
主要检查以下内容:
- 域名能否正常解析。
- 端口是否对公网开放。
- 连接是否超时。
- HTTPS 证书是否有效。
- 请求是否发生异常重定向。
- 网关、WAF、CDN 或反向代理是否拦截了 POST 请求。
- 接口是否受到登录鉴权、CSRF 校验或访问白名单的拦截。
这类测试请求不包含支付宝签名,业务接口返回验签失败是正常现象。需要确认的是请求能否到达服务器,以及访问日志中是否留下了记录。
第四步:从访问日志判断问题在哪一侧
查看回调时间段内的 Nginx、网关和应用日志:
grep '/api/alipay/notify' /var/log/nginx/access.log
根据日志结果,可以很快缩小排查范围。
完全没有访问记录
优先检查:
- 本次支付请求是否携带了正确的
notify_url。 - 域名、端口和路径是否正确。
- 内网穿透隧道是否在线。
- DNS 是否刚刚修改,尚未生效。
- 防火墙、安全组或 WAF 是否拦截了支付宝请求。
- 沙箱应用、沙箱买家账号和沙箱网关是否混用。
- 沙箱环境是否暂时异常。
有访问记录,但状态码不是 200
查看应用异常日志和反向代理配置。常见状态码包括:
301、302:回调地址发生了重定向。401、403:请求被登录鉴权、CSRF、WAF 或权限规则拦截。404:接口路径或部署上下文错误。405:接口不允许POST请求。500:回调代码发生异常。502、504:反向代理无法连接应用,或者处理超时。
状态码是 200,但支付宝仍重复通知
HTTP 200 只表示请求成功返回,不代表支付宝已经确认处理成功。响应正文必须是以下纯文本:
success
不要返回 JSON:
{"code":200,"message":"success"}
也不要返回这些内容:
SUCCESS
success
<html>success</html>
框架统一响应包装、调试输出、异常页面和多余空白,都可能让支付宝无法识别成功响应。
第五步:正确接收参数并验证签名
支付宝异步通知通常使用 application/x-www-form-urlencoded 提交,不能只按 JSON 请求体接收。
Spring Boot 示例:
@PostMapping(
value = "/api/alipay/notify",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public String handleAlipayNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((key, values) -> {
params.put(key, String.join(",", values));
});
try {
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return "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");
// 1. 校验 app_id 是否属于当前沙箱应用
// 2. 根据 out_trade_no 查询本地订单
// 3. 校验订单金额和收款方信息
// 4. 幂等更新订单状态
// 5. 保存 trade_no 和通知原始参数
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
return "success";
}
return "success";
} catch (Exception e) {
// 记录异常,避免输出密钥等敏感信息
return "failure";
}
}
验签时要特别检查:
- 使用支付宝公钥,而不是应用公钥。
- 公钥属于当前沙箱应用。
charset与请求配置一致,通常是UTF-8。sign_type与配置一致,例如RSA2。- 验签前没有修改、解码或删除参与签名的参数。
- 不要自行拼接参数再验签,应优先使用 SDK 提供的方法。
- 沙箱密钥不能与正式环境密钥混用。
如果近期重新生成过密钥、切换过证书模式,或者修改过沙箱应用配置,需要重新核对本地配置。
第六步:检查框架拦截规则
回调接口接收的是服务器之间的请求,不能要求支付宝携带用户登录状态。应根据项目的安全策略,为回调路径放行登录校验,并妥善处理 CSRF。
Spring Security 的配置示意如下,具体写法取决于所用版本:
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/alipay/notify").permitAll()
.anyRequest().authenticated()
);
如果项目启用了 CSRF,不要因为一个回调接口而关闭整个系统的 CSRF 防护。只需为该接口配置忽略规则,并通过支付宝签名验证请求的真实性。
第七步:做好主动查询补偿
异步通知不应成为唯一的订单确认机制。网络中断、服务重启和沙箱波动都可能造成通知延迟或处理失败。
以下情况可以调用 alipay.trade.query:
- 用户支付后返回商户页面,但本地订单尚未更新。
- 订单长时间停留在待支付状态。
- 回调处理发生异常。
- 定时任务发现订单状态不一致。
更新订单时必须保证幂等性。例如,可以通过条件更新避免重复处理:
UPDATE payment_order
SET status = 'PAID',
alipay_trade_no = ?
WHERE out_trade_no = ?
AND status = 'PENDING';
即使支付宝重复发送通知,订单也只能完成一次状态迁移,不能因此重复发货、记账或发放权益。
如果确认是沙箱环境异常
只有同时满足以下条件,才比较有理由怀疑沙箱本身出现了临时波动:
- 订单查询结果显示支付成功。
- 支付请求中的
notify_url正确。 - 回调地址可以稳定地从公网访问。
- 服务端在对应时间段完全没有收到请求。
- 应用、账号、网关和密钥属于同一个沙箱环境。
- 重新创建测试订单后,问题仍能稳定复现。
沙箱环境不能按生产级服务的稳定性来要求。遇到这种情况,可以稍后重新测试,同时保留订单号、支付宝交易号、支付时间和完整请求日志,供后续定位使用。不要伪造支付宝回调来直接修改真实业务订单。本地联调可以构造请求测试接口流程,但最终仍要通过真实的沙箱支付验证签名和整条通知链路。
备注:内容仅供参考。
