支付宝电脑版扫码支付成功但未收到异步通知
结论
最常见的原因是:支付请求没有传入 notify_url,或该地址无法由支付宝服务器通过公网访问。
商家后台配置的某些“回调地址”可能用于授权、应用网关等场景,并不会自动成为支付接口的异步通知地址。以电脑网站支付 alipay.trade.page.pay 为例,发起支付时需要明确设置 notify_url。
浏览器能正常跳转支付,或用户可以手动打开通知地址,都不能证明支付宝服务器能够访问该地址。
优先检查支付请求
发起电脑网站支付时,需要同时设置:
return_url:支付完成后,浏览器同步跳转的地址。notify_url:支付宝服务器主动调用的异步通知地址。
Java SDK 示例:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl("https://www.example.com/alipay/return");
request.setNotifyUrl("https://www.example.com/alipay/notify");
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001",
"total_amount": "99.00",
"subject": "订单支付",
"product_code": "FAST_INSTANT_TRADE_PAY"
}
""");
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
String paymentForm = response.getBody();
如果使用其他语言或自行组装请求,也要确认最终提交给支付宝的公共请求参数中确实包含:
notify_url=https://www.example.com/alipay/notify
除了检查项目配置文件,还要记录实际发出的请求参数,确认代码分支、环境变量或 SDK 封装没有覆盖 notify_url。
检查通知地址是否真正可用
异步通知由支付宝服务器发起,通常使用 POST 表单提交。通知地址需要满足以下条件:
- 必须是可通过公网访问的完整 URL,不能使用:
localhost
127.0.0.1
192.168.x.x
10.x.x.x
- 域名解析正确,服务器可以通过公网访问。
- HTTPS 证书有效,证书链完整且没有过期。
- 接口不依赖浏览器登录状态、Cookie 或验证码。
- 请求不会被防火墙、WAF、CDN、反向代理或 IP 白名单拦截。
- 地址不会跳转到登录页,也不会发生 HTTP 到 HTTPS 或其他地址的重定向。
- 接口允许支付宝发送
POST请求,而非只接受浏览器的GET请求。
“手动访问没有问题”通常只能说明浏览器的 GET 请求正常。可以从公网环境模拟一次表单请求:
curl -i -X POST \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=test&trade_status=TRADE_SUCCESS' \
https://www.example.com/alipay/notify
该请求只能用于检查网络、路由和请求处理,不能代替支付宝签名验证。
正确处理异步通知
通知接口需要读取支付宝提交的全部表单参数,然后完成验签和业务校验。处理成功后,响应体必须是以下纯文本:
success
Java Servlet 风格示例:
@PostMapping("/alipay/notify")
@ResponseBody
public String notify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((name, values) -> {
params.put(name, String.join(",", values));
});
try {
boolean verified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (!verified) {
return "failure";
}
String appIdFromNotify = 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");
String sellerId = params.get("seller_id");
// 必须校验 app_id、seller_id、订单金额和订单号。
// 更新订单时要保证幂等,防止重复通知造成重复记账。
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
// 查询本地订单并核对金额、商户身份
// 原子地更新订单状态
}
return "success";
} catch (AlipayApiException e) {
return "failure";
}
}
不要先读取请求体,再用不兼容的方式解析参数,否则参与验签的参数可能不完整。验签时使用支付宝公钥,不要使用应用公钥或商户私钥。
排查服务器是否收到请求
先在通知接口的最外层记录访问日志,包括:
- 请求时间
- HTTP 方法
- 请求路径
- 请求参数名称
- HTTP 状态码
- 异常信息
- 请求处理耗时
不要把敏感字段和完整签名直接写入公开日志。
可以根据日志快速判断问题所在:
- 完全没有访问记录:检查实际支付请求中的
notify_url,以及公网连通性、DNS、证书、防火墙和网关路由。 - 有访问记录,但业务没有更新:检查验签、参数解析、订单号、金额校验和事务处理。
- 收到多次通知:接口可能没有及时、准确地返回纯文本
success,也可能在处理过程中超时或发生异常。 - 返回 301、302、401、403、404、405、500:根据状态码检查重定向、鉴权、路由、请求方法或服务器异常。
通知接口的响应应尽量简单:
HTTP/1.1 200 OK
Content-Type: text/plain; charset=UTF-8
success
不要返回:
{"code":200,"message":"success"}
也不要返回 HTML 页面、额外空格、调试信息或统一响应对象。
确认交易是否真实成功
浏览器显示“支付成功”或跳转到 return_url,不能作为最终入账依据。应使用商户订单号调用订单查询接口,例如:
alipay.trade.query
需要核对:
out_trade_no是否与本地订单一致;- 交易状态是否已经成功;
total_amount是否一致;app_id和收款方身份是否属于当前商户;- 发起支付与接收通知是否使用同一套应用和环境配置。
如果订单查询结果显示未支付、交易已关闭,或订单号不是预期值,就不会产生预期的成功通知。
容易忽略的配置问题
还需要检查以下情况:
- 沙箱环境与正式环境的配置混用;
- 实际运行节点仍在使用旧配置;
- 多台服务器中只有部分节点配置了通知路由;
notify_url中的查询参数没有正确编码;- 网关只放行
GET,没有放行POST; - CSRF 中间件拦截了支付宝请求;
- 通知接口要求 Bearer Token、Session 或登录状态;
- Web 应用部署路径发生变化,导致通知地址返回 404;
- 接口处理时间过长,尚未返回
success就已超时; - 验签字符集或签名类型与请求不一致。
推荐的处理顺序
- 打印并核对发起支付时实际使用的
notify_url。 - 确认该地址属于当前应用和当前运行环境。
- 从外部网络发送
POST请求,检查状态码和响应内容。 - 查看 Web 服务器、反向代理、WAF 和应用日志。
- 使用
alipay.trade.query核实交易的真实状态。 - 收到通知后完成验签、商户身份校验、金额校验和幂等更新。
- 业务处理成功后,只返回纯文本
success。
异步通知可能重复发送,也可能延迟到达,因此订单系统不能只依赖单次通知。更稳妥的做法是以异步通知为主,并提供主动查询补偿机制,同时保证订单更新具有幂等性。
备注:内容仅供参考。
