PAYATHON 2026

支付宝支付成功后未触发异步回调,如何排查?

支付阿杰

结论

Postman 能成功请求回调地址,只说明该地址在当前网络环境下可以接收请求。它不能证明支付宝也能访问这个地址,也不能证明下单时已将 notify_url 正确提交给支付宝。

排查时主要检查三件事:

  1. 下单请求是否真的传入了 notify_url,参数位置是否正确;
  2. 支付宝服务器能否通过公网访问该地址;
  3. 请求是否被网关、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'

测试时应同时查看各层日志:

  1. CDN 或 WAF 访问日志;
  2. 负载均衡日志;
  3. Nginx、Apache 等反向代理日志;
  4. 应用入口日志;
  5. 业务处理日志。

如果只有业务日志,没有网关层日志,很容易把”请求被网关拒绝”误判成”支付宝没有发送请求”。

浏览器或 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 有记录而应用没有,则检查反向代理路由、请求大小限制、超时设置和应用过滤器。

推荐的排查顺序

按以下顺序检查,通常能较快定位问题:

  1. 查询交易状态,确认该笔订单确实支付成功;
  2. 核对订单所属环境、app_id、商户号和商户订单号;
  3. 查看实际发往支付宝网关的请求,确认其中有正确的 notify_url;
  4. 确认 notify_url 没有被误放进 biz_content;
  5. 从外部网络检查域名、证书、端口和回调路径;
  6. 查看 CDN、WAF、负载均衡和 Web 服务器的访问日志;
  7. 检查登录认证、CSRF、请求格式限制和 IP 白名单;
  8. 确认接口可以接收表单格式的 POST 请求;
  9. 检查验签参数、公钥、字符集和签名类型;
  10. 确保处理成功后快速返回纯文本 success。

如果支付宝侧完全没有这笔通知的记录,服务器所有入口日志中也找不到请求,应先回到下单请求,确认 notify_url 是否真的传入。Postman 请求成功不能替代这项检查。

备注:内容仅供参考。