PAYATHON 2026

支付宝沙箱支付突然收不到异步回调,如何排查?

支付阿杰

结论

支付宝沙箱突然收不到异步通知,通常不是支付接口失效,而是下面某个环节出了问题:

  • 本次支付请求没有传入有效的 notify_url。
  • 回调地址无法从公网访问,比如内网穿透地址失效、域名解析异常或防火墙拦截。
  • HTTPS 证书过期、证书链不完整,或者 TLS 配置不兼容。
  • 支付宝已经发出通知,但接口响应超时,或者返回内容不是 success。
  • 应用公钥、支付宝公钥、签名方式或沙箱应用配置发生了变化。
  • 支付宝沙箱环境暂时异常。

排查时,先确认订单确实支付成功,再查看服务端访问日志。这样可以判断支付宝究竟是没有发送通知,还是已经发送,但服务端处理失败。

第一步:确认订单是否真的支付成功

异步通知只会在交易状态发生相应变化时触发。先使用沙箱买家账号完成付款,再调用 alipay.trade.query 查询订单状态。

需要关注以下结果:

  • TRADE_SUCCESS:交易支付成功。
  • TRADE_FINISHED:交易完成。
  • WAIT_BUYER_PAY:买家尚未付款。
  • 查询不到订单:检查 out_trade_no、沙箱网关和应用环境是否一致。

如果订单仍是 WAIT_BUYER_PAY,没有收到支付成功通知是正常的。

主动查询可以用来确认和补偿订单状态,但不能证明支付宝一定发送过异步通知。

第二步:检查实际请求中的 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);

检查时注意:

  • return_url 是浏览器的同步跳转地址。
  • notify_url 是支付宝服务器发送异步通知的地址。
  • 用户关闭支付页面不会影响 notify_url。
  • 只配置 return_url 不会自动产生异步通知。
  • 如果全局配置和请求对象都设置了地址,需要确认最终使用的是哪个值。
  • notify_url 不要包含 #fragment,也不能依赖浏览器 Cookie 或用户登录状态。

即使昨天和今天的代码完全相同,也要检查环境变量、配置中心、容器配置和实际部署版本是否发生了变化。

第三步:确认回调地址可以被公网访问

支付宝服务器无法访问以下地址:

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

使用内网穿透工具时,免费隧道地址可能会在重启后改变,也可能临时离线。这是出现”昨天正常,今天突然失效”的常见原因。

可以通过非本机网络测试回调地址,例如使用手机蜂窝网络访问,或者从公网服务器执行:

curl -i -X POST https://example.com/api/alipay/notify \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'test=1'

主要检查以下内容:

  • 域名能否正常解析。
  • 端口是否对公网开放。
  • 连接是否超时。
  • HTTPS 证书是否有效。
  • 请求是否发生异常重定向。
  • 网关、WAF、CDN 或反向代理是否拦截了 POST 请求。
  • 接口是否受到登录鉴权、CSRF 校验或访问白名单的拦截。

这类测试请求不包含支付宝签名,业务接口返回验签失败是正常现象。需要确认的是请求能否到达服务器,以及访问日志中是否留下了记录。

第四步:从访问日志判断问题在哪一侧

查看回调时间段内的 Nginx、网关和应用日志:

grep '/api/alipay/notify' /var/log/nginx/access.log

根据日志结果,可以很快缩小排查范围。

完全没有访问记录

优先检查:

  • 本次支付请求是否携带了正确的 notify_url。
  • 域名、端口和路径是否正确。
  • 内网穿透隧道是否在线。
  • DNS 是否刚刚修改,尚未生效。
  • 防火墙、安全组或 WAF 是否拦截了支付宝请求。
  • 沙箱应用、沙箱买家账号和沙箱网关是否混用。
  • 沙箱环境是否暂时异常。

有访问记录,但状态码不是 200

查看应用异常日志和反向代理配置。常见状态码包括:

  • 301、302:回调地址发生了重定向。
  • 401、403:请求被登录鉴权、CSRF、WAF 或权限规则拦截。
  • 404:接口路径或部署上下文错误。
  • 405:接口不允许 POST 请求。
  • 500:回调代码发生异常。
  • 502、504:反向代理无法连接应用,或者处理超时。

状态码是 200,但支付宝仍重复通知

HTTP 200 只表示请求成功返回,不代表支付宝已经确认处理成功。响应正文必须是以下纯文本:

success

不要返回 JSON:

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

也不要返回这些内容:

SUCCESS
success 
<html>success</html>

框架统一响应包装、调试输出、异常页面和多余空白,都可能让支付宝无法识别成功响应。

第五步:正确接收参数并验证签名

支付宝异步通知通常使用 application/x-www-form-urlencoded 提交,不能只按 JSON 请求体接收。

Spring Boot 示例:

@PostMapping(
    value = "/api/alipay/notify",
    consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public String handleAlipayNotify(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");

        // 1. 校验 app_id 是否属于当前沙箱应用
        // 2. 根据 out_trade_no 查询本地订单
        // 3. 校验订单金额和收款方信息
        // 4. 幂等更新订单状态
        // 5. 保存 trade_no 和通知原始参数

        if ("TRADE_SUCCESS".equals(tradeStatus)
                || "TRADE_FINISHED".equals(tradeStatus)) {
            return "success";
        }

        return "success";
    } catch (Exception e) {
        // 记录异常,避免输出密钥等敏感信息
        return "failure";
    }
}

验签时要特别检查:

  • 使用支付宝公钥,而不是应用公钥。
  • 公钥属于当前沙箱应用。
  • charset 与请求配置一致,通常是 UTF-8。
  • sign_type 与配置一致,例如 RSA2。
  • 验签前没有修改、解码或删除参与签名的参数。
  • 不要自行拼接参数再验签,应优先使用 SDK 提供的方法。
  • 沙箱密钥不能与正式环境密钥混用。

如果近期重新生成过密钥、切换过证书模式,或者修改过沙箱应用配置,需要重新核对本地配置。

第六步:检查框架拦截规则

回调接口接收的是服务器之间的请求,不能要求支付宝携带用户登录状态。应根据项目的安全策略,为回调路径放行登录校验,并妥善处理 CSRF。

Spring Security 的配置示意如下,具体写法取决于所用版本:

http.authorizeHttpRequests(auth -> auth
        .requestMatchers("/api/alipay/notify").permitAll()
        .anyRequest().authenticated()
);

如果项目启用了 CSRF,不要因为一个回调接口而关闭整个系统的 CSRF 防护。只需为该接口配置忽略规则,并通过支付宝签名验证请求的真实性。

第七步:做好主动查询补偿

异步通知不应成为唯一的订单确认机制。网络中断、服务重启和沙箱波动都可能造成通知延迟或处理失败。

以下情况可以调用 alipay.trade.query:

  • 用户支付后返回商户页面,但本地订单尚未更新。
  • 订单长时间停留在待支付状态。
  • 回调处理发生异常。
  • 定时任务发现订单状态不一致。

更新订单时必须保证幂等性。例如,可以通过条件更新避免重复处理:

UPDATE payment_order
SET status = 'PAID',
    alipay_trade_no = ?
WHERE out_trade_no = ?
  AND status = 'PENDING';

即使支付宝重复发送通知,订单也只能完成一次状态迁移,不能因此重复发货、记账或发放权益。

如果确认是沙箱环境异常

只有同时满足以下条件,才比较有理由怀疑沙箱本身出现了临时波动:

  • 订单查询结果显示支付成功。
  • 支付请求中的 notify_url 正确。
  • 回调地址可以稳定地从公网访问。
  • 服务端在对应时间段完全没有收到请求。
  • 应用、账号、网关和密钥属于同一个沙箱环境。
  • 重新创建测试订单后,问题仍能稳定复现。

沙箱环境不能按生产级服务的稳定性来要求。遇到这种情况,可以稍后重新测试,同时保留订单号、支付宝交易号、支付时间和完整请求日志,供后续定位使用。不要伪造支付宝回调来直接修改真实业务订单。本地联调可以构造请求测试接口流程,但最终仍要通过真实的沙箱支付验证签名和整条通知链路。

备注:内容仅供参考。