支付宝沙箱这两天是否正在维护,支付成功后为何未回调?
结论
仅凭“支付链接昨天不可用,今天支付成功但没有回调”,还不能判断支付宝沙箱正在维护。沙箱偶尔会出现不稳定或通知延迟,但没有官方公告时,不宜直接归因于平台维护。
既然支付已经成功,更应先检查异步通知地址 notify_url、公网网络、签名验证、接口响应和沙箱配置。建议先调用 alipay.trade.query 查询订单状态,再逐项检查回调链路。
为什么支付成功却没有回调
支付宝支付涉及两个容易混淆的地址:
return_url:支付完成后,用户浏览器跳转到这个地址,通常用于展示支付结果。notify_url:支付宝服务器向这个地址发起 POST 请求,用于异步通知支付结果。
用户看到支付成功页面,只能说明浏览器完成了跳转,不能证明 notify_url 已经调用成功。常见原因有:
- 支付请求中没有正确传入
notify_url。 - 回调地址使用了
localhost、127.0.0.1或局域网 IP,支付宝服务器无法访问。 - 域名、HTTPS 证书、端口、防火墙或反向代理配置有误。
- 回调接口发生重定向、处理超时或返回 HTTP 错误。
- 接口收到了通知,但验签失败,或者业务代码抛出了异常。
- 回调处理成功后,没有返回纯文本
success。 - 沙箱应用的
app_id、支付宝公钥、应用私钥或网关环境混用了。 - 订单号重复,或者业务系统已经处理过这笔订单。
- 沙箱通知出现延迟或临时异常。
排查步骤
1. 查询支付宝侧的订单状态
使用创建支付时的 out_trade_no 调用 alipay.trade.query。
重点检查这些字段:
trade_statustrade_noout_trade_nototal_amount
如果查询结果是 TRADE_SUCCESS 或 TRADE_FINISHED,说明支付宝侧已经支付成功,接下来应检查异步通知链路。如果查询不到订单,需要确认应用、网关和订单号是否属于同一个沙箱环境。
订单查询也可以作为主动补偿手段,但不能完全代替异步通知。
2. 确认支付请求设置了 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);
notify_url 必须是支付宝服务器可以直接访问的公网地址。该接口不能依赖浏览器登录状态,也不应要求额外的自定义请求头。
3. 从公网验证回调地址
至少需要确认:
- 域名可以正常解析。
- HTTPS 证书有效,证书链完整。
- 回调路径允许 POST 请求。
- 网关、防火墙和 WAF 没有拦截支付宝请求。
- 接口不会自动跳转到登录页。
- 服务可以在合理时间内完成处理。
- Nginx、应用服务器和业务日志中是否出现过相关请求。
本地开发时,可以通过合规的内网穿透服务提供临时公网 HTTPS 地址。测试地址发生变化后,支付请求中的 notify_url 也要同步更新。
4. 正确读取参数并验签
支付宝异步通知通常通过表单参数提交,不能只按 JSON 请求体解析。
Spring Boot 示例:
@PostMapping(
value = "/api/alipay/notify",
produces = "text/plain;charset=UTF-8"
)
public String handleNotify(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");
if (!expectedAppId.equals(appId)) {
return "failure";
}
Order order = orderService.findByOrderNo(outTradeNo);
if (order == null) {
return "failure";
}
if (order.getAmount().compareTo(new BigDecimal(totalAmount)) != 0) {
return "failure";
}
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
orderService.markPaidIdempotently(outTradeNo, tradeNo);
}
return "success";
} catch (Exception e) {
return "failure";
}
}
验签使用的是支付宝公钥,不是应用公钥或应用私钥。沙箱和正式环境的密钥也要分别配置,不能混用。
5. 返回纯文本 success
业务处理成功后,响应体应直接返回:
success
不要返回:
{"code":200,"message":"success"}
也不要返回 HTML 页面、统一响应对象或包含其他说明的字符串。只有识别到符合要求的成功响应后,支付宝才会认为通知已经处理完成,否则可能按照平台机制重试。
建议增加的日志
可以在回调入口记录必要的诊断信息:
log.info(
"Alipay notify received: outTradeNo={}, tradeNo={}, tradeStatus={}",
params.get("out_trade_no"),
params.get("trade_no"),
params.get("trade_status")
);
生产日志中不要完整输出私钥、签名配置或其他敏感信息。建议分别记录:
- 是否进入回调接口
- 请求方法和 Content-Type
out_trade_notrade_notrade_status- 验签结果
- 金额和应用 ID 的校验结果
- 最终响应内容
- 异常堆栈
如果 Web 服务器的访问日志中完全没有请求,问题更可能出在通知地址、公网网络或支付宝沙箱侧。如果有请求但业务状态没有更新,则应重点检查参数解析、验签和异常处理。
注意事项
回调处理必须具备幂等性。同一笔订单可能收到多次通知,系统不能因此重复发货、充值或记账。可以根据订单状态、out_trade_no 唯一约束或支付流水表实现幂等控制。
支付结果不能只依赖 return_url。浏览器跳转可能被关闭、拦截或伪造,最终状态应以验签通过的异步通知为主,同时结合 alipay.trade.query 主动核对。
如果公网地址、密钥和回调代码都已确认正常,订单查询也显示支付成功,但长时间没有收到任何通知请求,可以再检查支付宝开放平台公告和沙箱状态,或者向支付宝技术支持提交订单号、请求时间及 trade_no。得到官方确认前,应表述为“疑似沙箱通知异常”,不要直接认定支付宝正在维护。
备注:内容仅供参考。
