扫码支付测试环境收不到异步通知,但Postman回调正常
结论
测试环境的回调接口能通过 Postman 调通,只能说明当前客户端可以访问该接口,不能说明支付宝异步通知服务器也能正常访问。
正式环境正常而测试环境异常时,应先检查测试域名的公网解析、网络访问控制、WAF、防火墙、重定向及证书配置。然后确认下单请求是否传入了正确的 notify_url,并检查应用是否已经收到请求,只是因为验签失败、参数校验失败或返回内容不符合要求,被支付平台判定为通知失败。
常见原因
1. 测试域名没有对所有公网来源开放
测试环境常见的访问限制包括:
- 防火墙仅允许办公网络、VPN 或部分出口 IP 访问。
- WAF、负载均衡或网关配置了来源 IP 白名单。
- 域名在内网和公网解析到不同地址。
- 接口依赖内网 DNS、Host 映射或专线网络。
- 安全设备拦截了支付宝通知服务器的请求。
- 公网入口已经开放,但请求转发到测试服务时失败。
Postman 与支付宝服务器使用的来源 IP、DNS 和网络链路不同。因此,即使 Postman 调用成功,也不能排除这些问题。
可以从公网环境执行基础检查:
curl -v -X POST \
'http://test-hcz-member.pingan.com.cn/annualInspect-app/provider/payCallBack/alipay' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'out_trade_no=VI140002068418_237&trade_status=TRADE_SUCCESS'
同时检查域名解析:
nslookup test-hcz-member.pingan.com.cn
确认解析结果是公网可访问的入口,而不是内网 IP、失效 IP,或者仅向特定网络开放的地址。
2. 下单时没有传入正确的 notify_url
支付宝异步通知地址通常由创建交易时传入的 notify_url 决定。页面跳转地址、应用后台配置地址和异步通知地址并不是同一个概念。
检查生成二维码时实际发送的下单请求,确认其中包含:
notify_url=http://test-hcz-member.pingan.com.cn/annualInspect-app/provider/payCallBack/alipay
重点检查以下内容:
- 测试环境是否读取了错误的配置文件。
notify_url是否为空,或仍然指向正式环境。- 网关请求参数是否经过 URL 编码。
- 生成二维码后是否修改过配置。已经创建的交易通常不会因为后续修改配置而自动更换通知地址。
- 使用服务商模式时,通知地址是否放在了正确的一层请求参数中。
不要只查看配置文件。应记录最终发送给支付网关的完整公共参数,同时避免在日志中输出私钥等敏感信息。
3. 回调地址发生了重定向
测试域名可能存在以下跳转:
HTTP -> HTTPS
域名 A -> 域名 B
未登录请求 -> 登录页
接口路径 -> 统一错误页
支付平台发送的是服务器通知,不能依赖浏览器行为,也不能假设通知发送方一定会按预期处理 301、302 或登录跳转。
可以用下面的命令查看是否发生重定向:
curl -v -X POST \
'http://test-hcz-member.pingan.com.cn/annualInspect-app/provider/payCallBack/alipay'
如果返回 301 或 302,应直接将 notify_url 配置为最终处理请求的地址,并确保该地址不需要登录、验证码或 Cookie。
条件允许时,可以使用公网可访问的 HTTPS 地址,但证书必须有效,证书链应完整,证书中的域名也要与访问域名匹配。
4. 请求到达了网关,但没有进入业务应用
所谓“没有收到通知”,有时是请求已经到达 Nginx、负载均衡或 WAF,只是在后续环节被拦截或转发失败。
可以按照请求链路依次检查:
- DNS 解析记录。
- 公网负载均衡或反向代理访问日志。
- WAF 和防火墙拦截日志。
- Nginx、Apache 等入口日志。
- 应用容器访问日志。
- 回调方法入口日志。
- 验签和业务处理日志。
例如,检查 Nginx 是否收到了请求:
location /annualInspect-app/provider/payCallBack/alipay {
proxy_pass http://annual_inspect_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;
}
如果入口日志中没有相关请求,问题通常出在公网访问链路或通知地址配置。如果入口日志中有请求,但应用日志中没有,则需要检查路由、反向代理配置和应用部署状态。
5. 安全框架拦截了回调请求
支付宝异步通知是服务器之间的请求,不会携带用户登录态或站点生成的 CSRF Token。如果测试环境启用了更严格的安全策略,接口可能返回:
401 Unauthorized403 Forbidden404 Not Found- CSRF 校验失败
- 登录页面 HTML
回调路径通常需要排除登录认证和 CSRF 校验,但支付宝签名验证仍然必须保留。
以 Spring Security 为例,具体写法取决于项目使用的版本:
http.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/annualInspect-app/provider/payCallBack/alipay"
).permitAll()
.anyRequest().authenticated()
);
如果启用了 CSRF,还需要针对这个服务器回调路径做相应处理。允许匿名访问不代表可以信任请求,业务代码仍须验签并校验交易信息。
6. 接收参数的方式不正确
支付宝异步通知通常通过 POST 表单参数发送,不能只按照 JSON 请求体接收。如果 Postman 测试使用的是 JSON,即使调用成功,也没有覆盖真实通知的请求格式。
Spring MVC 可以直接读取表单参数:
@PostMapping(
value = "/provider/payCallBack/alipay",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
@ResponseBody
public String alipayNotify(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
request.getParameterMap().forEach((name, values) -> {
params.put(name, String.join(",", values));
});
// 先验签,再进行订单和金额校验
boolean valid = verifyAlipaySignature(params);
if (!valid) {
return "failure";
}
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
// 幂等处理订单状态
processPayment(outTradeNo, tradeNo, tradeStatus);
return "success";
}
verifyAlipaySignature 和 processPayment 只是示意方法,需要替换为项目实际使用的支付宝 SDK 验签逻辑和订单处理逻辑。
7. 验签或业务校验失败
如果应用已经收到请求,但处理过程中报错,也可能表现为“没有通知”。常见原因包括:
- 测试环境配置了正式环境的支付宝公钥。
- 把应用公钥误当成支付宝公钥。
charset配置不一致。- 读取参数后又对参数值进行了不恰当的解码。
- 验签时遗漏参数,或修改了原始参数值。
- 回调中的
app_id与本地配置不一致。 - 金额、商户号或订单号校验失败。
- 数据库异常导致事务回滚。
- 同一通知重复到达时触发唯一键异常。
验签通过后,至少还应核对:
app_id
out_trade_no
total_amount
seller_id
trade_status
具体需要校验哪些商户身份字段,应以实际接入模式和所使用的支付宝接口文档为准。不能只因为签名验证通过,就直接更新订单状态。
8. 接口没有按要求返回成功结果
业务处理成功后,需要返回支付平台要求的纯文本成功标识。对于支付宝异步通知,通常是:
success
不要返回 JSON、HTML 页面或带有额外包装的内容,例如:
{"code": 200, "message": "success"}
下面的写法更清楚:
@PostMapping("/provider/payCallBack/alipay")
@ResponseBody
public String alipayNotify(HttpServletRequest request) {
try {
Map<String, String> params = extractParams(request);
if (!verifyAlipaySignature(params)) {
return "failure";
}
handleNotifyIdempotently(params);
return "success";
} catch (Exception e) {
// 记录异常,但不要记录私钥等敏感信息
return "failure";
}
}
接口还需要尽快返回结果。对于耗时操作,可以先可靠地记录通知并完成必要校验,再交给内部任务处理,以免支付平台等待超时。
建议的排查顺序
针对当前情况,可以按以下顺序定位问题:
- 查看支付下单日志,确认这笔交易最终提交的
notify_url。 - 查看测试环境的公网入口、WAF 和 Nginx 日志,搜索
2020-07-06 17:37:52前后的请求。 - 检查回调 URL 是否返回
301、302、401、403或5xx。 - 确认接口支持
application/x-www-form-urlencoded格式的POST请求。 - 检查测试环境的支付宝公钥、
app_id、字符集和网关环境配置。 - 检查应用是否已经处理成功,却返回了 JSON、HTML 或其他不符合要求的内容。
- 检查订单处理是否支持重复通知,避免重复请求引发异常。
- 使用支付宝侧提供的交易查询或通知排查能力,确认交易状态和通知发送情况。具体入口及字段以当前开放平台控制台和接口文档为准。
回调处理的注意事项
- 异步通知可能出现延迟、重复或乱序,不能假设只会发送一次。
- 使用
out_trade_no关联业务订单时,还应保存并核对trade_no。 - 订单更新必须保证幂等。已支付订单再次收到成功通知时,应直接返回
success。 - 不能只根据
trade_status更新订单。必须先验签,再校验金额和商户身份。 - 前端支付完成页面不能代替异步通知。
- 如果长时间没有收到通知,可以主动查询交易状态作为补偿,但主动查询不能代替对回调链路的修复。
- 不建议把固定的通知来源 IP 白名单作为唯一安全措施。来源地址可能调整,签名验证才是主要的身份校验手段。
结合“正式环境正常、测试环境 Postman 正常”这一现象,应优先检查测试环境的公网入口限制、WAF 和防火墙策略、回调地址重定向,以及下单请求实际携带的 notify_url。
备注:内容仅供参考。
