PAYATHON 2026

沙箱APP支付成功后未收到回调接口请求

支付阿杰

结论

沙箱 APP 支付成功却没有收到回调,问题通常不在客户端的支付结果,而在以下几个环节:异步通知地址 notify_url 没有正确传入;支付宝服务器无法直接访问回调地址;通知已经到达,但被网关、HTTPS 配置或业务代码拦截。

APP 支付状态应以服务端收到的异步通知为准。客户端返回 9000,只能说明支付流程已经成功结束,不能代替服务端回调。

先确认是否传入了 notify_url

服务端调用 APP 支付接口时,需要在请求参数中传入完整的异步通知地址,例如:

https://pay.example.com/api/alipay/notify

注意以下几点:

  • notify_url 是异步通知地址,支付宝服务器会向该地址发送 POST 请求。
  • APP 支付一般不依赖 return_url。return_url 主要用于网页同步跳转,不能代替异步通知。
  • notify_url 应在服务端生成支付订单时设置,不能只写在 APP 客户端代码中。
  • 地址必须包含协议和完整路径,不能使用相对地址。

以 Java SDK 的常见写法为例:

AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
request.setBizContent(objectMapper.writeValueAsString(bizContent));
request.setNotifyUrl("https://pay.example.com/api/alipay/notify");

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
String orderInfo = response.getBody();

不同版本 SDK 的类名和调用方式可能略有差异,请以项目实际使用的支付宝开放平台 SDK 为准。

检查回调地址能否从公网访问

浏览器能够打开回调地址,不代表支付宝服务器可以正常调用它。需要从公网环境检查以下项目:

  1. 回调域名能否正常解析。
  2. 服务器的 80 或 443 端口是否开放。
  3. URL 是否存在跳转,例如从 HTTP 自动跳转到 HTTPS。
  4. 接口是否要求登录、Cookie、Token 或其他鉴权信息。
  5. 防火墙、WAF、CDN 或反向代理是否拦截了支付宝的 POST 请求。
  6. HTTPS 证书是否有效,证书链是否完整,证书域名是否匹配。
  7. 回调接口是否支持 application/x-www-form-urlencoded 格式。

回调地址不能使用以下类型的地址:

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

本地调试时,需要使用公网可访问的测试服务器,或者通过带有公网 HTTPS 地址的内网穿透服务转发请求。是否允许 HTTP、非标准端口或特定 TLS 配置,应以当前支付宝开放平台的接入要求为准。

可以先用 curl 模拟表单 POST,确认请求能够通过网关并进入应用:

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

这个命令只能检查接口连通性,无法模拟带有合法支付宝签名的通知。

在接口入口记录原始请求

排查时不能只看业务处理日志。请求刚进入回调方法时,就应记录请求时间、请求参数和请求来源,然后再进行验签和业务处理。

Spring Boot 示例:

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

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

    log.info("Received Alipay notify: {}", params);

    // 后续执行验签和业务处理
    return "success";
}

如果 Nginx 开启了访问日志,也要检查对应路径是否出现 POST 请求:

location /api/alipay/notify {
    proxy_pass http://127.0.0.1:8080;
}

可以根据日志结果缩小排查范围:

  • Nginx 没有记录:检查 notify_url、DNS、防火墙、CDN 和 HTTPS。
  • Nginx 有记录,但应用没有记录:检查路由、反向代理和请求体限制。
  • 应用有记录,但订单状态没有变化:检查验签、参数解析和业务异常。
  • 应用处理成功,但平台仍在重复通知:检查响应内容是否为纯文本 success。

正确验签并核对订单信息

收到通知后,不能直接更新订单。必须使用支付宝公钥验签,并核对应用、商户订单、金额和卖家信息。

Java 示例:

@PostMapping("/api/alipay/notify")
public String alipayNotify(HttpServletRequest request) {
    try {
        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) {
            log.warn("Alipay notify signature verification failed");
            return "failure";
        }

        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 appId = params.get("app_id");
        String sellerId = params.get("seller_id");

        // 必须查询本地订单并核对:
        // 1. out_trade_no 是否存在
        // 2. app_id 是否为当前应用
        // 3. seller_id 是否为预期收款账号
        // 4. total_amount 是否与本地订单金额一致

        if ("TRADE_SUCCESS".equals(tradeStatus)
                || "TRADE_FINISHED".equals(tradeStatus)) {
            paymentService.markPaid(
                outTradeNo,
                tradeNo,
                new BigDecimal(totalAmount)
            );
        }

        return "success";
    } catch (Exception e) {
        log.error("Failed to process Alipay notify", e);
        return "failure";
    }
}

这里的 alipayPublicKey 是支付宝公钥,不是应用私钥,也不是应用公钥。沙箱和生产环境的应用配置、网关、密钥及账号不能混用。

如果使用证书模式,应按照当前 SDK 的证书验签方式处理,不能直接使用普通公钥模式的初始化代码。

检查是否混用了沙箱和生产配置

线上回调正常,只能证明线上域名和线上应用配置可用,无法证明沙箱订单参数正确。沙箱环境需要逐项核对:

  • 创建订单时使用的是否为沙箱网关。
  • app_id 是否属于沙箱应用。
  • 签名私钥是否与沙箱应用配置匹配。
  • 验签使用的支付宝公钥是否属于同一套沙箱配置。
  • 付款账号是否为沙箱买家账号。
  • 收款账号是否为对应的沙箱卖家账号。
  • 实际提交的订单参数中是否包含预期的 notify_url。

除了检查配置文件,还要记录最终发送给支付网关的请求参数。多环境项目中,环境变量覆盖、配置中心未刷新或实例未重启,都可能导致实际运行配置与预期不一致。

回调接口必须返回纯文本 success

通知处理完成后,应直接返回:

success

不要返回 JSON:

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

也不要返回 HTML、重定向页面,或由统一响应对象包装后的内容。Spring Boot 可以明确返回纯文本:

@PostMapping(
    value = "/api/alipay/notify",
    produces = "text/plain;charset=UTF-8"
)
@ResponseBody
public String alipayNotify(HttpServletRequest request) {
    // 验签并处理订单
    return "success";
}

只有验签通过、订单信息核对无误,而且业务处理成功后,才能返回 success。处理失败时不要返回成功,否则可能失去通过通知重试修正订单状态的机会。

排查框架和安全组件的拦截

回调请求由支付宝服务器发起,不会携带当前网站用户的登录状态。需要确认回调路径没有被以下机制拦截:

  • 登录认证拦截器;
  • Spring Security 鉴权;
  • CSRF 校验;
  • API 网关签名校验;
  • Referer 或 User-Agent 白名单;
  • 请求频率限制;
  • IP 白名单;
  • WAF 的表单或敏感参数规则。

Spring Security 项目可以根据实际版本放行回调路径。下面的代码仅用于说明配置意图:

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

允许回调请求通过网络和登录鉴权,并不等于降低支付安全性。接口仍需通过支付宝签名验证和订单信息核对,判断请求是否合法。

用主动查询补偿异步通知

异步通知不应是确认支付状态的唯一方式。客户端返回支付成功后,服务端可以使用商户订单号调用交易查询接口,再根据查询结果更新订单。

常用的查询接口是:

alipay.trade.query

示例:

AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent(
    "{\"out_trade_no\":\"" + outTradeNo + "\"}"
);

AlipayTradeQueryResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    String tradeStatus = response.getTradeStatus();

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        paymentService.markPaid(
            response.getOutTradeNo(),
            response.getTradeNo(),
            response.getTotalAmount()
        );
    }
}

查询结果也需要核对订单号、金额和收款方等信息。具体字段和 SDK 方法以项目使用的 SDK 版本为准。

可以采用下面的处理流程:

客户端完成支付
      ↓
服务端等待异步通知
      ↓
通知到达:验签、核对订单、幂等更新
      ↓
通知未及时到达:主动查询交易状态
      ↓
定时任务继续扫描未确认订单

建议的排查顺序

按照以下顺序检查,通常可以较快找到问题:

  1. 打印创建支付订单时实际提交的 notify_url。
  2. 确认沙箱网关、沙箱 app_id 和沙箱密钥属于同一套配置。
  3. 从公网环境向回调地址发送 POST 请求。
  4. 检查 DNS、HTTPS 证书、防火墙、CDN、WAF 和 Nginx 日志。
  5. 检查应用入口日志,确认请求是否被鉴权或 CSRF 拦截。
  6. 确认接口能够解析表单参数,而不是只接收 JSON。
  7. 检查验签公钥、字符集和签名类型是否正确。
  8. 确认处理成功后返回的是纯文本 success。
  9. 使用 alipay.trade.query 查询该沙箱订单的实际交易状态。
  10. 为未确认订单增加定时查询和人工补单能力。

支付通知可能重复到达,也可能因网络问题出现延迟,因此订单更新必须具备幂等性。可以将商户订单号作为唯一条件,只允许订单从“待支付”变更为“已支付”,防止重复通知引起重复发货、重复记账或重复增加余额。

备注:内容仅供参考。