PAYATHON 2026

支付宝支付成功后未收到回调,如何排查?

支付小周

结论

五一前后的其他订单都能正常收到异步通知,因此回调地址存在整体配置错误的可能性不高。这笔订单更可能遇到了以下情况:

  • 支付宝发送通知时,回调服务短暂不可达、响应超时,或者返回的内容不是 success;
  • 请求被网关、WAF、CDN、负载均衡或应用程序拦截;
  • 应用收到了通知,但在验签、订单匹配或业务处理环节失败;
  • 这笔支付请求实际使用的 notify_url 与预期地址不同;
  • 通知已经处理,但业务日志、订单状态更新或消息消费发生异常。

只凭内部订单编号和回调地址,无法确定支付宝是否发送过通知。应先根据支付请求中的商户订单号查询支付宝交易状态,再对照支付宝侧的通知记录和服务器日志逐步定位。

第一步:确认支付宝侧交易状态

内部订单编号 M23042922002413459 未必是提交给支付宝的 out_trade_no。先从本地支付记录中确认以下信息:

  • 支付宝商户订单号 out_trade_no
  • 支付宝交易号 trade_no
  • 实际支付时间
  • 创建支付时传入的 notify_url
  • 使用的 app_id 和商户账号

然后调用 alipay.trade.query 查询交易状态,重点查看 trade_status:

  • TRADE_SUCCESS:支付成功,可以继续排查异步通知。
  • TRADE_FINISHED:交易已经完成,也说明支付成功。
  • WAIT_BUYER_PAY:买家尚未付款。
  • TRADE_CLOSED:交易已经关闭。

Java SDK 示例:

AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent("""
{
  "out_trade_no": "实际提交给支付宝的商户订单号"
}
""");

AlipayTradeQueryResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    System.out.println("tradeNo: " + response.getTradeNo());
    System.out.println("tradeStatus: " + response.getTradeStatus());
    System.out.println("buyerPayAmount: " + response.getBuyerPayAmount());
} else {
    System.out.println("code: " + response.getCode());
    System.out.println("subCode: " + response.getSubCode());
    System.out.println("subMsg: " + response.getSubMsg());
}

如果通过 out_trade_no 查不到交易,且已经取得 trade_no,也可以改用支付宝交易号查询。具体调用方式以当前接入版本的官方文档为准。

第二步:核对该笔订单实际使用的回调地址

不能只看系统现在的配置,还要确认创建这笔支付请求时,实际传给支付宝的 notify_url。

预期地址为:

https://api-app.wangpaiyixue.cn/v1/pay/alipay_notify

逐项核对以下细节:

  • 协议是否为 https
  • 域名和路径是否完全一致
  • 是否使用了测试环境、旧域名或旧配置
  • 是否误将 notify_url 放入业务参数
  • 经过 SDK 编码后是否被覆盖
  • 五一期间是否发布过配置、切换过环境或更换过网关

异步通知地址通常应作为支付宝接口的公共请求参数提交,不能放在 biz_content 中。

第三步:检查支付宝侧通知记录

在支付宝商家平台或开放平台中查找这笔交易的异步通知记录。平台界面和菜单名称可能调整,以当前账号中实际显示的内容为准。

需要确认:

  • 是否生成过异步通知;
  • 一共发送了几次;
  • 每次通知的发送时间;
  • 回调地址是否正确;
  • HTTP 状态码;
  • 请求是否超时;
  • 支付宝是否将响应内容识别为 success。

如果平台提供的通知明细不足,需要带上 app_id、out_trade_no、trade_no 和支付时间,向支付宝技术支持查询。只有内部订单编号与 out_trade_no 相同时,支付宝才能直接使用该编号定位交易。

第四步:按支付时间检查服务器链路

检索日志时,应覆盖实际支付时间以及之后的一段时间,因为支付宝可能会重试异步通知。不要只查订单创建时刻的日志。

可以按以下顺序检查:

  1. CDN、WAF 或云安全日志;
  2. 负载均衡和 API 网关日志;
  3. Nginx、Ingress 等入口访问日志;
  4. 应用请求日志;
  5. 验签和业务处理日志;
  6. 数据库、缓存及消息队列记录。

例如,可以按回调路径检索 Nginx 日志:

rg '/v1/pay/alipay_notify' /path/to/nginx/access.log

如果历史日志已经归档,还要检查压缩归档、日志平台和云服务访问日志。

根据请求在哪一层消失,可以迅速缩小排查范围:

  • 入口层完全没有记录:检查支付宝是否发送了通知,以及域名解析、网络、TLS、WAF和入口服务是否正常。
  • 入口层有记录,但应用没有记录:检查网关转发、路由配置和负载均衡。
  • 应用收到请求,但返回 4xx 或 5xx:检查参数解析、验签和业务异常。
  • 应用返回 200,但响应不是纯文本 success:支付宝仍可能将通知判定为失败。
  • 回调已经处理,订单状态却没有更新:检查事务回滚、条件更新、消息消费和订单映射。

同时核对五一期间是否发生过证书过期、DNS 变更、服务器重启、扩缩容、数据库连接耗尽、网关发布或安全策略调整。

第五步:检查回调处理代码

支付宝异步通知通常通过 HTTP POST 发送表单参数。处理程序需要读取全部参数,并使用支付宝公钥验签。验签通过后,还要校验交易归属和金额。

Java 示例:

@PostMapping(
    value = "/v1/pay/alipay_notify",
    consumes = "application/x-www-form-urlencoded"
)
public ResponseEntity<String> alipayNotify(HttpServletRequest request) {
    Map<String, String> params = new HashMap<>();

    request.getParameterMap().forEach((name, values) -> {
        if (values != null && values.length > 0) {
            params.put(name, values[0]);
        }
    });

    try {
        boolean verified = AlipaySignature.rsaCheckV1(
            params,
            ALIPAY_PUBLIC_KEY,
            "UTF-8",
            params.getOrDefault("sign_type", "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");

        // 必须校验 app_id、商户订单号、订单金额以及交易归属。
        // 金额应使用 BigDecimal 比较,不要使用 double。
        // 更新操作必须具备幂等性,避免重复通知造成重复入账。
        processPaymentIdempotently(
            appId,
            outTradeNo,
            tradeNo,
            tradeStatus,
            new BigDecimal(totalAmount)
        );

        return ResponseEntity.ok("success");
    } catch (Exception e) {
        // 记录异常及必要的定位字段,但不要记录私钥等敏感信息。
        return ResponseEntity.ok("failure");
    }
}

只有验签、订单校验及必要的业务处理全部成功后,才能返回:

success

响应中不能带有 JSON、HTML、空格或其他说明。以下返回值可能不会被支付宝识别为成功:

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

正确处理重复通知

支付宝可能重复发送异步通知,因此回调接口必须支持幂等处理。可以为 trade_no 和 out_trade_no 建立唯一约束,并通过状态条件控制更新:

UPDATE payment_order
SET pay_status = 'PAID',
    alipay_trade_no = :trade_no,
    paid_at = :paid_at
WHERE out_trade_no = :out_trade_no
  AND pay_status = 'UNPAID';

如果更新行数为 0,需要重新查询订单:

  • 订单已经是支付成功状态:将其作为重复通知处理,可以返回 success;
  • 订单不存在、金额不一致或交易号冲突:记录告警,不能直接确认成功。

不能只根据 trade_status 更新订单,至少还要校验:

  • app_id 是否属于当前应用;
  • out_trade_no 是否存在;
  • total_amount 是否与本地应付金额一致;
  • trade_no 是否已经关联到其他订单;
  • 交易状态是否属于允许入账的状态。

该笔历史订单如何补偿

如果查询结果表明订单已经支付成功,但本地仍显示未支付,可以通过人工或自动对账完成补偿:

  1. 调用 alipay.trade.query 获取支付宝侧的真实状态;
  2. 校验 app_id、订单号、交易号和金额;
  3. 保存完整的查询结果和操作记录;
  4. 通过幂等业务接口将订单修正为已支付;
  5. 补发系统内部的支付成功事件;
  6. 检查权益发放、课程开通、库存和财务流水是否也需要补偿。

不建议伪造支付宝回调请求,再直接调用生产回调接口。这种做法无法证明原始签名有效,还可能绕过交易归属和金额校验。应建立独立的对账补偿流程,并保留审计记录。

注意事项

回调接口应尽快完成验证和落库。耗时较长的业务可以先写入消息队列,再异步执行。如果必须等所有下游流程完成后才返回 success,任何一个下游服务超时,都可能导致支付宝重复发送通知。

日志可以保留 notify_id、trade_no、out_trade_no、trade_status、HTTP 状态码、响应耗时和验签结果,但不能记录应用私钥、支付宝公钥原文或其他敏感凭证。

考虑到节前和节后的回调都正常,排查时应优先核对这笔交易实际使用的 notify_url、支付宝侧的通知发送记录,以及五一期间的回调链路是否出现过短时故障。最终结论应以支付宝交易查询结果和双方日志为依据。

备注:内容仅供参考。