手机 H5 支付后 notify_url 无法接收支付宝通知
结论
通常不需要在支付宝应用网关中配置 notify_url。
手机网站支付(alipay.trade.wap.pay)的异步通知地址由支付请求中的 notify_url 指定。支付成功后,支付宝服务器会向该地址发送 HTTP POST 请求。
浏览器能打开这个地址,只能证明浏览器发出的 GET 请求可以访问,不能证明支付宝服务器发送的 POST 通知能够到达,也不能证明业务系统可以正确处理通知。
排查步骤
1. 确认支付请求中传入了 notify_url
以支付宝 Java SDK 为例:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl("https://www.example.com/pay/return");
request.setNotifyUrl("https://www.example.com/api/alipay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001",
"total_amount": "0.01",
"subject": "订单支付",
"product_code": "QUICK_WAP_WAY"
}
""");
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
String paymentForm = response.getBody();
return_url 和 notify_url 的作用不同:
return_url:支付结束后跳转用户浏览器,用于展示页面。notify_url:由支付宝服务器主动调用,用于确认支付结果并更新订单。
业务系统不能只根据 return_url 判断支付是否成功。
2. 确认通知地址可以从公网访问
notify_url 需要满足以下条件:
- 使用完整的公网 URL。
- 域名可以通过公网 DNS 正常解析。
- 端口可以从公网访问,建议优先使用标准的
443端口。 - HTTPS 证书有效,证书链完整,证书与域名匹配。
- 不使用
localhost、内网 IP 或只能通过公司 VPN 访问的地址。 - 接口不要求登录,不依赖 Cookie,也不会跳转到登录页。
- 网关、防火墙、CDN 或 WAF 不会拦截支付宝发出的
POST请求。
可以在业务服务器之外测试 POST 请求:
curl -i -X POST \
'https://www.example.com/api/alipay/notify' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=test&trade_status=TRADE_SUCCESS'
这项测试只能检查网络和路由是否正常,不能代替真实通知的验签测试。
3. 确认后端路由接受 POST 请求
支付宝异步通知通常使用 application/x-www-form-urlencoded 格式提交参数。如果接口只支持 GET,即使浏览器可以正常访问,支付宝发送的 POST 请求仍可能返回 404、405,或者被框架拒绝。
Spring Boot 示例:
@PostMapping(
value = "/api/alipay/notify",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public String alipayNotify(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 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. 校验 app_id、seller_id 是否属于当前商户
// 2. 根据 out_trade_no 查询本地订单
// 3. 校验订单金额和通知金额是否一致
// 4. 判断 trade_status 是否为业务认可的支付状态
// 5. 使用事务和幂等机制更新订单
return "success";
} catch (AlipayApiException exception) {
return "failure";
}
}
示例中的 alipayPublicKey 是支付宝公钥,不是应用公钥或应用私钥。具体如何获取公钥,取决于应用使用的是密钥模式还是证书模式。
4. 正确返回响应内容
通知处理成功后,接口需要直接返回以下纯文本:
success
处理响应时需要注意:
- 不要返回 JSON,例如
{"result":"success"}。 - 不要返回 HTML 页面。
- 不要在
success前后添加其他业务文本。 - 不要返回重定向响应。
- 尽快完成处理并返回响应。耗时任务可以交给内部队列异步执行。
如果验签失败、订单信息不一致或业务处理失败,可以返回:
failure
如果没有返回支付宝认可的成功响应,支付宝可能按照其通知机制重试。具体的重试次数和时间间隔,以当前接入文档为准。
5. 检查网关和安全策略
一般不需要在支付宝开放平台中配置“应用网关”。不过,如果系统前面部署了 API Gateway、Nginx、CDN 或 WAF,仍要确认通知路径已经正确放行。
Nginx 示例:
location = /api/alipay/notify {
proxy_pass http://payment-service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 5s;
proxy_read_timeout 15s;
}
需要重点检查:
- 是否只允许浏览器常见的
User-Agent。 - 是否拦截表单 POST。
- 是否启用了 CSRF 校验。
- 是否对没有携带 Token 的请求返回
401或403。 - 请求是否因限流、IP 白名单或地区策略被拦截。
- 是否将 HTTP 请求强制跳转到 HTTPS。
- 多层网关之间是否存在路径重写错误。
对于 Spring Security,可以只让通知路径忽略 CSRF,不要关闭整个系统的 CSRF 防护:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.ignoringRequestMatchers("/api/alipay/notify"));
return http.build();
}
通知接口免登录,不代表可以省略安全校验。接口仍须通过支付宝签名、商户身份、订单号和金额等信息验证通知。
为什么浏览器能打开,通知仍然收不到
常见原因有:
- 浏览器发送的是
GET请求,而接口没有开放POST。 - 浏览器携带了登录 Cookie,支付宝服务器没有。
- 通知接口被 CSRF、鉴权、WAF 或防火墙拦截。
- HTTPS 证书存在兼容性问题。
- 支付请求中没有实际传入
notify_url,或传入了其他环境的地址。 - 路由返回了
301、302、401、403、404、405或500。 - 接口收到了通知,但验签时使用了错误的支付宝公钥。
- 参数读取方式有误,导致用于验签的原文发生变化。
- 业务处理成功后,接口没有直接返回
success。 - 应用部署在多台服务器上,但只检查了其中一台的日志。
安全与业务注意事项
异步通知可能重复到达,所以订单更新必须具备幂等性。例如,只有待支付订单才能首次更新为已支付状态。已经支付的订单再次收到相同通知时,应直接返回 success,不要重复扣减库存或发放权益。
处理通知时,至少要核对以下参数:
app_idseller_idout_trade_nototal_amounttrade_status
不能只根据 trade_status 更新订单,也不能因为请求来自某个固定 IP 就跳过验签。支付宝的网络出口策略可能发生调整。是否支持 IP 白名单及其具体范围,应以当前官方接入说明为准。
如果接口日志中完全没有请求,先检查支付请求是否实际传入了 notify_url,再排查公网链路。如果日志中有请求,但订单没有更新,则重点检查 HTTP 状态码、验签结果、公钥配置、金额校验和接口最终返回的内容。
备注:内容仅供参考。
