如何解决:支付宝 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 完成一笔真实测试支付,再查看网关和应用访问日志,确认通知是否到达。
备注:内容仅供参考。
