支付宝支付成功后未收到异步回调通知如何排查?
结论
交易状态已经是 TRADE_SUCCESS,说明支付链路正常。没有收到异步通知,通常有以下几类原因:调用 alipay.trade.create 时没有正确传入通知地址、支付宝无法访问该地址、通知被网关拦截,或者接收程序没有正确记录请求并返回响应。
服务商代调用时,重点检查以下内容:
- 请求是否实际携带了
notify_url。 - 是否使用了正确的
app_id和商户授权令牌app_auth_token。 notify_url是否可以从公网直接访问。- 接口是否能够接收
POST表单数据。 - 验签和业务处理成功后,是否返回纯文本
success。
仅凭支付宝交易号 61104119354632320000 和 appid: 2021003173601122,无法判断支付宝是否发送过通知。还需要结合请求报文、支付宝侧的通知记录和服务器访问日志进行排查。
首先检查 notify_url
notify_url 是接口的公共请求参数,不属于业务 JSON。服务商通过 SDK 代调用时,需要在请求对象上设置通知地址,并在执行请求时传入被代调用商户的 app_auth_token。
以 Java SDK 为例:
AlipayTradeCreateRequest request = new AlipayTradeCreateRequest();
request.setNotifyUrl("https://pay.example.com/alipay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001",
"total_amount": "0.01",
"subject": "测试订单",
"buyer_id": "2088xxxxxxxxxxxx"
}
""");
AlipayTradeCreateResponse response =
alipayClient.execute(request, appAuthToken);
查看实际发往支付宝网关的请求日志,确认其中包含:
method=alipay.trade.create
app_id=2021003173601122
notify_url=https://pay.example.com/alipay/notify
app_auth_token=...
常见错误有:
- 通知地址只写在本地配置文件中,本次请求并未携带。
- 把
notify_url放进了biz_content。 - SDK 封装层漏掉了
setNotifyUrl()。 - 代调用时没有传入
app_auth_token,或者传入了其他商户的授权令牌。 - 多套环境使用了不同配置,生产请求却带上了测试环境的地址。
注意区分 return_url 和 notify_url。return_url 用于客户端同步跳转,不能代替服务端异步通知。
检查通知地址能否被支付宝访问
通知地址必须是支付宝服务器可以访问的公网 HTTP 或 HTTPS 地址。排查时需要注意:
- 域名能否在公网解析,解析结果是否正确。
- 服务器的
80或443端口是否开放。 - HTTPS 证书是否有效,证书链是否完整,证书域名是否匹配。
- 防火墙、WAF、CDN 或反向代理是否拦截了支付宝的
POST请求。 - 地址是否仅允许内网访问,或者要求固定来源 IP、登录态、客户端证书。
- 接口是否返回
301或302跳转。 - 接口是否响应超时,或者返回
4xx、5xx。 - 路由是否只接受
GET,无法处理POST。
排查时可以同时查看:
- CDN 或负载均衡访问日志
- Nginx、Apache 等 Web 服务器日志
- 应用网关日志
- 接口应用日志
- WAF 和防火墙拦截日志
如果最外层访问日志中完全找不到对应请求,先检查请求是否传入了 notify_url,再检查 DNS、网络入口和安全策略。如果网关记录了请求,但应用没有收到,则继续检查路由、代理转发和请求体解析。
正确接收并处理通知
支付宝通常通过 POST 发送异步通知,内容类型为 application/x-www-form-urlencoded,不能按 JSON 请求体解析。
在 Java Servlet/Spring 环境中,可以先收集全部表单参数,再使用支付宝 SDK 验签:
@PostMapping(
value = "/alipay/notify",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public String alipayNotify(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");
String sellerId = params.get("seller_id");
// 进一步校验 app_id、seller_id、out_trade_no、total_amount。
// 使用 out_trade_no 或 trade_no 做幂等更新。
// 只有业务处理成功后才能返回 success。
return "success";
} catch (AlipayApiException e) {
return "failure";
}
}
实际项目还应记录通知参数和处理结果,但日志中不要写入私钥、完整授权令牌等敏感信息。
检查验签配置
如果服务器收到了请求,但程序仍判定处理失败,问题通常出在验签配置或参数处理上:
- 验签应使用支付宝公钥,不能使用应用私钥或应用公钥。
sign_type通常是RSA2,具体应以实际通知参数和应用配置为准。- 字符集应与请求配置一致,常见值是
UTF-8。 - 验签前不要修改参数值、重新编码或丢弃空格。
- 不要手工把
sign、sign_type拼入待验签字符串,建议直接使用官方 SDK。 - 不要混用证书模式和普通公钥模式。
验签通过后,仍需完成业务校验,包括核对:
app_id
seller_id
out_trade_no
trade_no
total_amount
trade_status
app_id 应与本次调用使用的应用一致,seller_id 应属于实际收款商户,订单号和金额也必须与本地订单一致。
必须返回纯文本 success
只有验签和业务处理都成功后,接口才能返回:
success
响应正文不能包含 JSON、HTML、空格或其他说明。下面这种响应可能不会被识别为成功:
{"code":200,"message":"success"}
数据库事务尚未提交时,也不要提前返回 success。如果业务处理失败,应返回 success 以外的内容,让支付宝按其通知策略继续尝试。
同一通知可能重复到达,因此订单更新必须具备幂等性:
UPDATE payment_order
SET pay_status = 'PAID',
alipay_trade_no = ?
WHERE out_trade_no = ?
AND pay_status <> 'PAID';
推荐排查顺序
- 打印并核对创建交易时的完整公共请求参数,确认其中有正确的
notify_url。 - 确认代调用使用的是目标商户对应的
app_auth_token。 - 通过支付宝开放平台相关工具或账单、交易查询能力,确认交易所属的应用和商户。
- 查询支付宝侧是否有异步通知发送记录。具体入口名称可能会随开放平台后台调整。
- 查看 CDN、负载均衡和 Web 服务器访问日志,判断请求是否已经到达。
- 如果请求已经到达,检查 HTTP 状态码、表单解析、验签结果和业务异常。
- 确认处理成功后的响应体严格为
success。 - 修复后创建新订单重新测试。历史交易能否重发通知,应以开放平台当前提供的重发能力为准。
订单查询返回 TRADE_SUCCESS 时,可以据此进行补偿处理,但不能用主动查询代替异步通知。生产系统应增加主动查询或定时补偿任务。对于长时间停留在待支付状态的订单,调用交易查询接口,并在核对订单号、商户、金额和交易状态后进行幂等补单。
备注:内容仅供参考。
