PAYATHON 2026

如何解决:支付宝 api v3 电脑网页支付接口,没有发送异步回调通知?

支付小周

结论

异步通知没有发送,最可能是因为 notify_url 的位置不对。

从生成的支付 URL 来看,notify_url 被放进了 biz_content:

{
  "out_trade_no": "3463454377",
  "total_amount": 0.01,
  "subject": "测试回调",
  "product_code": "FAST_INSTANT_TRADE_PAY",
  "notify_url": "https://test.rzzx.com.cn/prod-api/orderpay/alipaynotify"
}

在 alipay.trade.page.pay 中,notify_url 属于公共请求参数,不是业务参数,需要通过 AlipayTradePagePayRequest#setNotifyUrl() 单独设置。放在 biz_content 里时,支付宝网关可能无法将其识别为异步通知地址。

正确的请求方式

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();

// notify_url 必须设置为公共参数
request.setNotifyUrl(
    "https://test.rzzx.com.cn/prod-api/orderpay/alipaynotify"
);

// 用户支付完成后,浏览器跳转的页面,可按需设置
request.setReturnUrl(
    "https://test.rzzx.com.cn/pay/result"
);

JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", "3463454377");
bizContent.put("total_amount", "0.01");
bizContent.put("subject", "测试回调");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");

// 不要在 biz_content 中设置 notify_url
request.setBizContent(bizContent.toJSONString());

AlipayTradePagePayResponse response = alipayClient.pageExecute(request);

if (response.isSuccess()) {
    String form = response.getBody();
    // 将 form 原样返回给浏览器
} else {
    throw new IllegalStateException(
        response.getCode() + ": " + response.getSubMsg()
    );
}

修正后,最终请求里的 notify_url 应与 biz_content、method、sign 等参数同级,不能放在 JSON 业务参数中。

异步通知接口示例

支付宝异步通知通常通过 POST 发送,数据格式是表单参数,不是 JSON。Spring Boot 可以这样接收:

@PostMapping(
    value = "/orderpay/alipaynotify",
    produces = "text/plain;charset=UTF-8"
)
public String alipayNotify(HttpServletRequest request) throws Exception {
    Map<String, String> params = new HashMap<>();

    Map<String, String[]> requestParams = request.getParameterMap();
    for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
        params.put(entry.getKey(), String.join(",", entry.getValue()));
    }

    boolean verified = AlipaySignature.rsaCheckV1(
        params,
        ALIPAY_PUBLIC_KEY,
        "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");

    // 除验签外,还应核对 app_id、订单号、金额和收款方信息
    if (!EXPECTED_APP_ID.equals(appId)) {
        return "failure";
    }

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        // 必须使用幂等方式更新订单,避免重复通知造成重复入账
        paymentService.confirmPaid(
            outTradeNo,
            tradeNo,
            new BigDecimal(totalAmount)
        );
    }

    // 业务处理成功后,必须直接返回小写 success
    return "success";
}

如果项目使用了网关、上下文路径或反向代理,还要确认外部地址:

https://test.rzzx.com.cn/prod-api/orderpay/alipaynotify

能够正确转发到上面的控制器。

建议的排查顺序

1. 检查最终支付请求

生成支付表单或 URL 后,确认请求中存在以下顶层参数:

notify_url=https://test.rzzx.com.cn/prod-api/orderpay/alipaynotify

如果只能在 biz_content 中找到它,说明设置方式仍然有误。

2. 确认订单确实支付成功

调用 pageExecute() 或打开支付页面,并不表示交易已经成功。异步通知通常要等交易状态发生变化后才会发送,因此需要使用一笔新订单完成实际支付测试。

out_trade_no 必须唯一。重复使用已支付、已关闭或状态异常的订单号,可能会干扰测试结果。

3. 从公网测试回调地址

回调地址必须能由支付宝服务器直接访问,不能依赖:

  • 内网 IP;
  • VPN;
  • 登录态或 Cookie;
  • 浏览器认证;
  • IP 白名单误拦截;
  • 仅允许前端跨域来源的限制。

可以先测试接口是否连通:

curl -i -X POST \
  "https://test.rzzx.com.cn/prod-api/orderpay/alipaynotify" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data "test=1"

由于这个请求没有支付宝签名,业务接口返回 failure 是正常的。这里主要看请求能否到达应用,不要求接口必须返回 success。

4. 检查 HTTPS 和反向代理

需要确认:

  • 域名能够正常解析;
  • TLS 证书有效,证书链完整;
  • HTTP 跳转到 HTTPS 时没有异常;
  • Nginx、WAF、CDN 没有拦截支付宝发送的 POST;
  • 代理层没有改写请求体和 Content-Type;
  • /prod-api 路径没有被重复添加或截断。

可以分别查看 Nginx、API 网关和应用的访问日志,确定请求停在哪一层。

5. 检查接口安全配置

如果项目使用 Spring Security、Sa-Token、Shiro 或自定义鉴权,需要放行通知接口。例如 Spring Security:

http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/orderpay/alipaynotify").permitAll()
    .anyRequest().authenticated()
);

如果外部请求路径带有 /prod-api,但网关转发后的应用内路径不包含此前缀,放行规则应按应用实际收到的路径配置。

还要检查 CSRF 配置。服务端通知接口受到 CSRF 校验保护时,可能直接返回 403:

http.csrf(csrf -> csrf
    .ignoringRequestMatchers("/orderpay/alipaynotify")
);

具体写法取决于项目使用的 Spring Security 版本。

回调处理注意事项

  • return_url 是浏览器同步跳转地址,不能作为支付成功的最终依据。
  • notify_url 是支付宝服务器调用的异步地址,两者用途不同。
  • 通知验签要使用支付宝公钥,不能使用应用公钥或应用私钥。
  • 验签前不要修改参数值和字符编码,也不要进行二次 URL 解码。
  • 验签成功后,还要核对 app_id、out_trade_no、total_amount 等业务信息。
  • 回调可能重复发送,因此订单更新必须保证幂等。
  • 处理成功后直接返回纯文本 success,不要包装成 JSON,例如不要返回 {"code":200}。
  • 不要先返回 success,再异步执行关键入账逻辑。否则内部处理失败后,可能无法通过通知重试恢复。
  • 回调接口应尽快完成处理。耗时业务可以在可靠落库后交给消息队列执行。

当前样例应先修正 notify_url 的设置位置。修正后,使用全新的 out_trade_no 完成一笔真实测试支付,再查看网关和应用访问日志,确认通知是否到达。

备注:内容仅供参考。