正式环境支付成功后收不到异步回调通知
结论
支付成功不等于异步通知一定能送达。仅凭订单号无法判断平台是否已经发送通知。根据现有信息,建议先排查以下回调地址:
http://api-live-test-mi.changyinshow.com/biznotify/payment_notify/alipay
通知失败的常见原因有:公网无法访问、DNS 解析异常、防火墙或 WAF 拦截、接口重定向、处理超时、返回内容不符合支付平台要求,以及通知到达后验签或业务处理失败。
先在支付平台后台查询订单 0732462022121514454348644469003 的通知记录,再结合网关和应用日志,确认通知具体停在哪一层。
排查步骤
1. 查询支付平台的通知记录
在支付平台后台通过订单号查询:
0732462022121514454348644469003
需要确认:
- 订单是否属于当前正式环境商户。
- 支付状态是否确实为成功。
- 创建订单时实际提交的
notify_url是否与预期一致。 - 平台是否发送过异步通知。
- 每次通知的时间、响应状态码、响应内容和失败原因。
- 通知是否仍在自动重试周期内。
如果后台没有发送记录,应检查下单请求中的通知地址、商户配置和订单环境。如果后台显示已经发送,再继续检查服务端访问链路。
2. 验证回调地址能否从公网访问
异步通知由支付平台服务器主动发起,因此回调地址必须能从公网访问。只在公司内网或服务器本机测试无法说明问题。
可以从外部网络执行:
curl -i -X POST \
'http://api-live-test-mi.changyinshow.com/biznotify/payment_notify/alipay' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'test_notify=1'
检查以下内容:
- 域名能否正常解析。
- 80 端口是否开放。
- 请求是否进入负载均衡、网关或 Nginx。
- 是否返回
301、302、403、404、405或5xx。 - 响应时间是否过长。
- 接口是否仅允许内网 IP 访问。
- WAF、安全组或防火墙是否拦截了支付平台的请求。
测试请求没有合法签名,业务接口返回验签失败属于正常现象,但网关和应用至少应当收到请求。
3. 检查正式环境配置
域名中含有 live-test。需要确认这只是测试命名,并且该域名确实指向正式环境可访问的公网服务。检查时应以正式环境实际加载的配置为准,不能只看配置文件内容。
例如:
payment:
alipay:
notify-url: http://api-live-test-mi.changyinshow.com/biznotify/payment_notify/alipay
还要确认:
- 正式环境是否覆盖了这项配置。
- 下单代码是否通过参数传入了其他
notify_url。 - 修改配置后,应用是否已经重新加载。
- 多实例部署时,各实例的配置是否一致。
- 回调域名是否指向正确的正式环境入口。
最终应以创建订单时实际发送给支付平台的请求参数为准。
4. 检查网关和应用日志
按照通知发生时间依次查询:
- DNS 和负载均衡日志;
- CDN、WAF 或 API 网关日志;
- Nginx access/error 日志;
- 应用访问日志;
- 回调业务日志和异常日志。
可以按接口路径检索 Nginx 日志:
grep '/biznotify/payment_notify/alipay' /var/log/nginx/access.log
grep '/biznotify/payment_notify/alipay' /var/log/nginx/error.log
如果 Nginx 中没有记录,应检查公网链路、域名解析和安全策略。如果 Nginx 有记录,但应用没有记录,需要检查反向代理路由。如果应用已经收到请求,则继续检查参数解析、验签、异常信息和最终响应内容。
5. 检查接口是否支持平台使用的请求方式
支付回调通常使用 POST,参数一般以 application/x-www-form-urlencoded 格式提交。接口不能只接受 JSON,也不能依赖登录状态或 CSRF Token。
以 Spring Boot 为例,可以先记录必要的通知信息,再进行验签和业务处理:
@PostMapping(
value = "/biznotify/payment_notify/alipay",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public ResponseEntity<String> paymentNotify(
@RequestParam Map<String, String> params) {
String orderId = params.get("out_trade_no");
String tradeStatus = params.get("trade_status");
log.info("payment notify received, orderId={}, tradeStatus={}",
orderId, tradeStatus);
boolean signatureValid = verifySignature(params);
if (!signatureValid) {
log.warn("payment notify signature verification failed, orderId={}",
orderId);
return ResponseEntity.ok("failure");
}
try {
paymentService.handleNotification(params);
return ResponseEntity.ok("success");
} catch (Exception e) {
log.error("payment notify processing failed, orderId={}", orderId, e);
return ResponseEntity.ok("failure");
}
}
verifySignature 必须使用支付平台正式环境对应的公钥、字符集和签名算法实现。排查问题时也不能跳过验签。
示例中的 "success" 和 "failure" 是支付宝类回调的常见返回形式。实际返回内容必须遵循当前接入产品的官方接口文档,不能直接照搬示例。
业务处理要求
验签前保留原始参数
验签时应使用支付平台发送的完整参数。不要提前修改参数编码、删除空值或重新拼装字段,否则可能导致验签失败。
日志中也不能输出完整的敏感信息。订单号和交易号可以按排查需要记录,密钥、授权信息及用户隐私字段不得写入日志。
核对订单归属和金额
验签成功后,还需检查:
- 商户订单号是否存在。
- 商户号、应用 ID 是否与当前系统一致。
- 实付金额和币种是否与本地订单一致。
- 通知中的交易状态是否属于可处理状态。
- 订单是否已经关闭、退款或完成。
不能仅凭回调中的“支付成功”字段直接更新订单。
保证幂等
支付平台可能多次发送同一笔通知,因此回调处理必须支持幂等。可以通过订单状态判断和数据库条件更新,避免重复入账:
UPDATE payment_order
SET status = 'PAID',
paid_at = CURRENT_TIMESTAMP
WHERE order_id = ?
AND status = 'UNPAID';
更新后检查受影响行数:
- 受影响行数为
1:本次状态更新成功。 - 受影响行数为
0:查询订单当前状态,判断是否已经处理,不能直接认定为失败。
发放权益、增加余额等操作也要设置唯一约束或幂等记录,不能依赖接口不会被重复调用。
尽快返回确认结果
回调接口不适合执行耗时较长的同步任务。建议按以下顺序处理:
- 接收参数;
- 完成验签和订单校验;
- 以幂等方式更新支付状态;
- 提交事务;
- 返回平台要求的成功响应;
- 通过消息队列或任务表处理后续业务。
即使业务已经处理成功,如果接口超时,或者返回了 HTML 页面、错误的响应内容,支付平台通常仍会将本次通知判定为失败并继续重试。
常见配置问题
还需检查这些容易遗漏的问题:
- 回调接口被登录鉴权拦截。
- Spring Security 或其他安全组件拦截匿名请求。
- CSRF 校验导致
403。 - Nginx 只允许
GET,而支付平台发送的是POST。 - HTTP 自动跳转到 HTTPS,但支付平台没有正确跟随跳转。
- HTTPS 证书过期、证书链不完整或域名不匹配。
- 网关改写路径后,请求没有转发到正确的服务。
- 应用只解析 JSON,没有解析表单参数。
- 正式环境使用了沙箱公钥或错误的商户配置。
- 接口抛出异常后,被统一异常处理器转换为
500。 - 业务已经处理成功,但最终响应内容不是平台要求的确认字符串。
建议的定位顺序
可以按以下顺序排查:
支付平台通知记录
→ 公网域名与端口
→ WAF/网关/Nginx 日志
→ 应用入口日志
→ 参数解析与验签
→ 订单校验和事务
→ HTTP 状态码及响应内容
如果平台后台显示已经向该地址发送通知,需要取得通知时间、平台记录的 HTTP 状态码和响应内容,并在同一时间范围内检索 Nginx 与应用日志。通过这些信息,可以判断问题发生在公网链路、网关转发还是应用处理阶段。
备注:内容仅供参考。
