PAYATHON 2026

当面付支付成功后未收到异步通知回调

支付小周

结论

支付日志显示支付成功,并不等于异步通知已经发送到你的回调地址。这个结果可能来自同步响应、轮询查询或主动调用 alipay.trade.query。这些流程与异步通知彼此独立。

当面付成功后没有收到回调,可以重点检查以下问题:

  • 发起交易时是否实际传入了 notify_url
  • notify_url 是否为支付宝服务器能够直接访问的公网地址
  • 回调请求是否被网关、HTTPS、鉴权、重定向或防火墙拦截
  • 接口返回的成功是否仅表示请求受理成功,而非交易最终成功
  • 回调程序是否正确接收表单参数,并返回纯文本 success

不能把异步通知作为唯一的交易确认方式。系统还需要主动查询和补偿处理机制。

先确认日志中的成功指什么

当面付流程中可能出现几种不同的成功状态:

  1. 调用接口成功
    这表示支付宝已经受理请求,不一定代表买家已经付款。

  2. 客户端或服务端轮询到支付成功
    这个状态通常来自 alipay.trade.query,只能说明查询时交易处于成功状态。

  3. 收到支付宝异步通知
    支付宝服务器主动请求本次交易设置的 notify_url。

所以,即使日志已经记录 TRADE_SUCCESS,也不能据此认定异步通知接口被调用过。

检查发起交易时是否设置了 notify_url

notify_url 必须随交易请求一起提交。只在本地配置文件中填写地址不会自动生效,也不能用页面跳转地址 return_url 代替它。

以 Java SDK 为例:

AlipayClient alipayClient = new DefaultAlipayClient(
    gatewayUrl,
    appId,
    merchantPrivateKey,
    "json",
    "UTF-8",
    alipayPublicKey,
    "RSA2"
);

AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setNotifyUrl("https://pay.example.com/alipay/notify");

request.setBizContent("""
{
  "out_trade_no": "ORDER_202609130001",
  "total_amount": "0.01",
  "subject": "测试订单"
}
""");

AlipayTradePrecreateResponse response = alipayClient.execute(request);

需要检查最终发送的请求,确认其中确实包含 notify_url。仅给业务对象或未生效的配置项赋值是不够的。

如果使用第三方封装库,还要确认该库是否真的将回调地址传给了支付宝。部分封装库的全局 notify_url 只对某些方法生效,具体行为取决于所用库及版本。

检查回调地址是否可以从公网访问

支付宝异步通知由支付宝服务器发起,下面这些地址通常无法正常接收通知:

http://localhost/alipay/notify
http://127.0.0.1/alipay/notify
http://192.168.1.10/alipay/notify

回调地址需要满足以下条件:

  • 使用可从公网访问的完整 URL
  • 域名可以正常解析
  • 端口已经向公网开放
  • HTTPS 证书有效,证书链完整
  • 不要求登录、Cookie、验证码或人工操作
  • 不依赖浏览器环境
  • 不会被 IP 白名单、WAF、防火墙或反向代理直接拦截
  • 尽量避免经过 301、302 等重定向

可以通过非本机网络测试接口是否可达,例如:

curl -i -X POST https://pay.example.com/alipay/notify \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "out_trade_no=TEST_ORDER&trade_status=TRADE_SUCCESS"

这个测试只能确认网络和应用路由是否正常,不能代替支付宝签名验证。

确认服务端可以接收 POST 表单

支付宝异步通知通常通过 POST 表单参数发送,并非 JSON 请求。如果接口只接受 application/json,请求可能尚未进入业务代码就返回 400 或 415。

Spring Boot 示例:

@PostMapping(
    value = "/alipay/notify",
    consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public String notify(HttpServletRequest request) {
    Map<String, String> params = new HashMap<>();

    request.getParameterMap().forEach((key, values) -> {
        params.put(key, String.join(",", values));
    });

    // 后续进行签名和业务数据校验
    return "success";
}

如果框架启用了 CSRF 校验、统一登录拦截器或 API Token 鉴权,需要对支付宝通知路由作相应处理。这里的放行只是允许请求进入回调代码,并不表示可以跳过支付宝签名验证。

正确验签并校验交易信息

收到通知后,不能只根据 trade_status 更新订单。至少需要完成以下检查:

  • 使用支付宝公钥验证签名
  • 校验 app_id
  • 校验商户订单号 out_trade_no
  • 检查支付宝交易号 trade_no 是否与现有记录冲突
  • 校验金额 total_amount
  • 校验收款方身份字段
  • 确认订单没有被其他交易支付
  • 对重复通知进行幂等处理

Java 验签示例:

boolean signVerified = AlipaySignature.rsaCheckV1(
    params,
    alipayPublicKey,
    "UTF-8",
    "RSA2"
);

if (!signVerified) {
    return "failure";
}

String tradeStatus = params.get("trade_status");
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String totalAmount = params.get("total_amount");

if ("TRADE_SUCCESS".equals(tradeStatus)
        || "TRADE_FINISHED".equals(tradeStatus)) {
    // 校验 app_id、订单号、金额和收款方信息
    // 使用数据库事务和唯一约束保证幂等
    // 更新订单状态
}

return "success";

参与验签的实际参数应遵循 SDK 的要求。不要自行修改、排序、转码或拼接参数后再验签。

必须按要求返回 success

业务处理完成后,响应体应直接返回以下纯文本:

success

不要返回 JSON、HTML 或附带额外说明的内容,例如:

{"code": 200, "message": "success"}

下面的响应虽然含有单词 success,仍可能不符合通知确认要求:

success received

还要保证 HTTP 状态码正常,并避免回调处理耗时过长。建议按以下顺序处理:

  1. 验签并校验关键字段。
  2. 快速、幂等地落库或写入可靠队列。
  3. 返回纯文本 success。
  4. 异步执行发货、积分和短信等耗时业务。

如果处理失败,应记录完整错误信息,并按照实际协议返回失败结果,以便通知重试机制再次投递。

检查服务器日志链路

业务应用中完全没有请求日志,不一定说明支付宝没有发起请求。可以按顺序检查:

  • CDN 或云负载均衡访问日志
  • WAF、安全组和防火墙日志
  • Nginx、Apache 或网关访问日志
  • 应用服务器访问日志
  • 回调控制器入口日志
  • 验签和订单处理日志

在 Nginx 日志中可以重点查找:

POST /alipay/notify

常见异常状态码包括:

  • 301、302:请求发生了重定向
  • 400:请求参数或协议不符合应用要求
  • 401、403:请求被鉴权、CSRF、WAF 或访问控制拒绝
  • 404:路由配置错误
  • 405:接口不允许 POST
  • 415:接口不支持表单 Content-Type
  • 500、502、504:应用或上游服务发生异常

记录通知参数时需要脱敏,不要把私钥、完整签名材料或其他敏感信息写入日志。

不要只依赖异步通知

网络请求可能出现延迟、失败或重复,订单系统必须具备主动查询补偿机制。

以下场景可以调用 alipay.trade.query:

  • 用户支付后,前端长时间停留在等待页面
  • 订单超过一定时间仍处于支付中
  • 收银端已经显示成功,但服务端没有收到异步通知
  • 对账时发现支付宝状态与本地状态不一致

伪代码示例:

if (order.isPaying() && order.hasTimedOut()) {
    TradeQueryResult result = queryAlipayTrade(order.getOutTradeNo());

    if ("TRADE_SUCCESS".equals(result.getTradeStatus())
            || "TRADE_FINISHED".equals(result.getTradeStatus())) {
        confirmPaymentIdempotently(
            order,
            result.getTradeNo(),
            result.getTotalAmount()
        );
    }
}

异步通知和主动查询最终应调用同一个幂等的确认支付方法,防止重复记账、重复发货或重复增加余额。

推荐排查顺序

  1. 抓取或记录发起交易时的实际请求,确认其中包含正确的 notify_url。
  2. 确认查询结果确实为 TRADE_SUCCESS 或相应的最终状态。
  3. 从公网测试回调 URL 的 DNS、HTTPS、端口和 POST 路由。
  4. 检查网关、Nginx、WAF 和应用访问日志。
  5. 排除重定向、登录鉴权、CSRF 和 Content-Type 限制。
  6. 检查验签过程,并确认回调响应体为纯文本 success。
  7. 使用新的商户订单号重新测试,避免旧订单状态影响判断。
  8. 增加 alipay.trade.query 定时补偿和对账机制。

如果发起请求已经明确携带有效的公网 notify_url,交易也已进入最终成功状态,但服务器所有入口日志都没有回调记录,应保留 app_id、out_trade_no、trade_no、接口响应和请求时间等信息,再通过对应支付平台的技术支持渠道核查通知投递记录。不要在工单或公开日志中提交应用私钥。

备注:内容仅供参考。