PAYATHON 2026

订单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 配置。记录过期或归档后,当前集合查不到属于正常情况,此时需要查询归档库、备份或历史日志平台。

备注:内容仅供参考。