订单1632918661124-190-1支付回调无日志问题排查
结论
仅凭应用侧“没有查到日志”,不能认定支付平台没有发送回调,也不能确认 MongoDB 中一定没有相关记录。要核实订单 1632918661124-190-1 是否存在支付回调日志,需要直接查询实际使用的 MongoDB 集合。
如果 1632918661 是 Unix 秒级时间戳,对应的北京时间约为 2021-09-29 20:31:01。可以先以这个时间为中心扩大查询范围,同时检索订单号、原始请求体、支付流水号和回调接口路径。
回调成功后进入消息队列,只能说明系统采用了这样的后续处理流程。消息队列中没有该订单,不代表回调请求没有到达。问题也可能出在日志写入、验签、异常处理或消息投递环节。
MongoDB 查询方法
以下集合名和字段名只是示例,需要替换成实际值。
先按完整订单号查询:
db.payment_callback_log.find({
orderNo: "1632918661124-190-1"
}).sort({
createTime: 1
})
如果订单号可能保存在请求体、响应内容或嵌套字段中,可以扩大查询范围:
db.payment_callback_log.find({
$or: [
{ orderNo: "1632918661124-190-1" },
{ "request.orderNo": "1632918661124-190-1" },
{ "request.outTradeNo": "1632918661124-190-1" },
{ requestBody: /1632918661124-190-1/ },
{ message: /1632918661124-190-1/ }
]
}).sort({
createTime: 1
})
如果订单号没有被完整保存,可以结合时间范围和回调接口查询。假设时间字段采用 UTC 的 BSON Date:
db.payment_callback_log.find({
createTime: {
$gte: ISODate("2021-09-29T11:30:00.000Z"),
$lte: ISODate("2021-09-29T13:30:00.000Z")
},
requestUri: /callback|notify|payment/i
}).sort({
createTime: 1
})
这个查询覆盖下单时间前后一小时。支付回调可能晚于下单时间,也可能多次重试。如果首次查询没有结果,可以继续扩大到当天,甚至支付平台允许的最大重试周期。
如果时间以秒级时间戳保存,可以这样查询:
db.payment_callback_log.find({
timestamp: {
$gte: 1632915000,
$lte: 1632922200
}
}).sort({
timestamp: 1
})
还要确认当前连接的是正确的环境、数据库和集合:
db.getName()
db.getCollectionNames()
db.payment_callback_log.getIndexes()
建议的排查顺序
1. 检查入口层访问记录
先查看负载均衡、API Gateway、Ingress、Nginx 或网关日志。如果入口层有请求,而应用日志中没有,通常说明请求在进入回调方法之前被拦截。常见情况包括:
- 域名、路由或回调地址配置错误;
- 网关返回
404、403、429或5xx; - IP 白名单、WAF 或限流规则拦截了请求;
- 请求被转发到其他实例或旧版本服务;
- HTTPS、证书或网络连接异常。
查询时不要只搜索订单号。有些网关只记录 URI、状态码和请求时间,不会保存请求体。
2. 检查所有应用实例
如果服务部署了多个实例,回调可能由其他实例处理。应按照回调 URI、请求时间、来源 IP、Trace ID 和支付流水号查询全部实例,也要检查已经下线或重启实例的历史日志。
同时确认日志采集是否存在延迟、过滤或丢失。可能的原因包括:
- 日志级别高于
INFO; - 容器重启后,本地日志没有被采集;
- 日志采集规则排除了回调路径;
- 异步日志缓冲区没有及时刷新;
- 查询使用了错误的索引、时间范围或时区。
3. 检查回调方法的日志位置
如果日志写在参数解析、解密或验签之后,那么在解析失败或验签异常时,这条业务日志不会产生。
入口日志应放在方法开始处,并通过 try...catch 记录异常:
@PostMapping("/payment/callback")
public String paymentCallback(HttpServletRequest request) {
String requestBody = readRequestBody(request);
log.info(
"payment callback received, uri={}, contentType={}, body={}",
request.getRequestURI(),
request.getContentType(),
maskSensitiveData(requestBody)
);
try {
PaymentCallback callback = parseCallback(requestBody);
verifySignature(callback);
saveCallbackLog(callback, requestBody);
sendPaymentMessage(callback);
return "success";
} catch (Exception e) {
log.error(
"payment callback failed, uri={}, body={}",
request.getRequestURI(),
maskSensitiveData(requestBody),
e
);
throw e;
}
}
生产环境中不要直接记录签名密钥、银行卡号、完整手机号或其他敏感信息。
4. 检查 MongoDB 写入过程
即使应用已经打印“收到回调”,MongoDB 写入仍可能失败。需要逐项检查:
- MongoDB 连接超时或连接池耗尽;
- 写入异常被捕获后没有继续抛出;
- 字段校验、唯一索引或文档大小限制导致写入失败;
- 数据实际写入了其他数据库、集合或环境;
- 时间字段采用 UTC,但查询时使用了北京时间;
- 集合配置了 TTL 索引,历史日志已被自动删除;
- 订单号保存在原始 JSON、XML 或加密字段中;
- MongoDB 采用异步写入,进程退出前尚未完成落库。
可以重点检查 TTL 索引:
db.payment_callback_log.getIndexes()
如果索引中存在 expireAfterSeconds,需要结合日志产生时间判断记录是否已经过期。即使现在查不到,也不能据此认定当时没有收到回调。
5. 区分“回调落库”和“消息投递”
当前流程至少包含以下几个相互独立的阶段:
支付平台发起回调
→ 网关接收
→ 应用进入回调方法
→ 解析及验签
→ MongoDB 写入
→ 消息队列投递
→ 消费者处理订单
任何一个阶段失败,都可能表现为“订单回调未成功”。建议分别记录各阶段的状态:
{
"orderNo": "1632918661124-190-1",
"callbackReceived": true,
"signatureVerified": true,
"mongoSaved": true,
"messageSent": false,
"failureStage": "MQ_SEND",
"failureReason": "timeout",
"createTime": "2021-09-29T12:31:01.000Z"
}
MongoDB 写入成功但消息发送失败时,系统应提供补偿或重试机制。如果业务要求严格避免消息丢失,可以考虑事务消息或本地消息表方案,不能只依赖一次普通消息投递。
没有查询到记录时怎么办
如果按完整订单号、时间范围、回调路径和支付流水号查询都没有结果,还需要向支付平台核对以下信息:
- 实际支付状态;
- 回调地址;
- 首次回调时间和各次重试时间;
- 回调请求 ID 或支付流水号;
- 平台收到的 HTTP 状态码和响应内容;
- 是否出现网络连接、证书或超时错误。
只有将支付平台的发送记录、入口网关日志和应用日志相互对照,才能判断回调是没有发出、没有到达,还是到达后处理失败。
注意事项
订单号前十位虽然符合 Unix 时间戳的形式,但如果没有相应的业务规则,不能断定它一定代表下单时间。查询应以订单系统保存的创建时间和支付平台记录为准。
如果日志产生时间距今较久,应优先确认 MongoDB 的数据保留期限、归档策略和 TTL 配置。记录过期或归档后,当前集合查不到属于正常情况,此时需要查询归档库、备份或历史日志平台。
备注:内容仅供参考。
