PAYATHON 2026

统一收单交易查询中out_trade_no查询不到订单

支付老李

结论

trade_no 是支付平台生成的全局交易号,而 out_trade_no 是商户自定义订单号,其查询通常受创建订单时的应用、商户身份和环境约束。

因此,出现“trade_no 能查到,out_trade_no 查不到”,通常不是订单状态问题,而是以下两类原因之一:

  1. 查询请求使用的 app_id、商户账号、授权身份或沙箱环境与创建订单时不一致。
  2. 查询时实际发送的 out_trade_no 与下单时的原始值并非完全一致,例如包含空格、换行、大小写差异,或者代码取错了订单号。

返回结果中显示的 out_trade_no 与输入值看起来相同,并不能完全排除上述问题;不可见字符和请求身份差异通常无法从页面显示中直接发现。

为什么 trade_no 可以查询成功

支付成功后,平台会为交易生成唯一的 trade_no。使用该字段查询时,系统可以直接定位具体交易。

out_trade_no 则由商户生成。平台查询时除了匹配订单号,还可能结合创建订单时的商户或应用上下文进行定位。即使字符串相同,只要查询请求与下单请求不在同一身份范围内,就可能返回“交易不存在”。

沙箱环境中尤其需要检查:

  • 下单和查询是否使用同一个沙箱应用的 app_id
  • 是否使用了同一套沙箱密钥和网关地址
  • 下单账号与查询账号是否属于同一个沙箱商户
  • 是否混用了普通调用和第三方应用授权调用
  • 使用授权调用时,app_auth_token 是否与下单时一致
  • 沙箱数据或应用配置是否曾被重置、切换

排查步骤

1. 以查询结果中的订单号为准进行逐字节比较

先通过 trade_no 查询订单,取得响应中的 out_trade_no,再与准备提交的值比较长度和字节内容,而不只是肉眼比较日志。

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

String expected = queryResponse.getOutTradeNo();
String actual = localOutTradeNo;

System.out.println("expected=[" + expected + "], length=" + expected.length());
System.out.println("actual=[" + actual + "], length=" + actual.length());

System.out.println(Arrays.toString(expected.getBytes(StandardCharsets.UTF_8)));
System.out.println(Arrays.toString(actual.getBytes(StandardCharsets.UTF_8)));
System.out.println("equals=" + expected.equals(actual));

重点检查首尾空格、换行符、全角字符以及大小写差异。不要直接使用 trim() 修改历史订单号后再查询;应先确认下单时实际提交的原始值。

2. 对比下单和查询请求的调用身份

核对两次请求中的以下信息:

gateway
app_id
method
seller_id(如果业务请求中使用)
app_auth_token(如果使用第三方应用授权)
sign_type
charset

其中 app_id、网关环境和授权上下文最关键。沙箱请求必须始终发送到沙箱网关,不能将正式环境与沙箱环境的配置混用。

3. 查询时只传一个查询条件

排查期间建议分别测试,不要同时传入 trade_no 和 out_trade_no。部分接口在两者同时存在时会优先使用 trade_no,这样即使 out_trade_no 有误,请求仍可能成功,容易造成误判。

AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();

AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(outTradeNo);
// 不要同时设置 model.setTradeNo(...)

request.setBizModel(model);

AlipayTradeQueryResponse response = alipayClient.execute(request);

使用 trade_no 查询时同样只设置一个字段:

AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();

AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setTradeNo(tradeNo);
// 不要同时设置 model.setOutTradeNo(...)

request.setBizModel(model);

AlipayTradeQueryResponse response = alipayClient.execute(request);

4. 检查数据库中的订单号映射

确认本地保存的 out_trade_no 确实是创建该笔支付时提交的商户订单号,而不是:

  • 业务订单号
  • 支付重试产生的新订单号
  • 退款单号
  • 合并支付中的子订单号
  • 展示用订单编号
  • 被截断或重新格式化后的编号

比较时应使用下单请求的原始报文,而不只依赖数据库当前值。

5. 保留完整请求信息继续定位

如果调用身份和订单号字节均一致,但问题仍能稳定复现,应记录:

  • 沙箱 app_id
  • 接口名称
  • 请求时间
  • trade_no
  • out_trade_no
  • 返回的 code、sub_code 和 sub_msg
  • SDK 实际发送的业务参数

提交问题时不要公开应用私钥、签名原文中的敏感信息或完整授权令牌。

注意事项

  • trade_no 查询成功,说明交易本身存在且支付状态可被读取。
  • 响应中的 out_trade_no 是订单创建时记录的值,不代表当前查询请求中的同名参数已成功匹配。
  • 商户订单号应在约定的商户或应用范围内保持唯一,不要在不同支付重试中随意复用。
  • 沙箱行为不能直接证明生产环境一定相同。若所有参数完全一致后仍只有沙箱复现,也可能是沙箱数据或路由异常,但在没有错误码和完整请求日志的情况下,不能直接认定为平台故障。
  • 最稳妥的处理方式是同时保存 out_trade_no 与支付成功后返回的 trade_no:日常业务按 out_trade_no 关联,异常排查时使用 trade_no 精确定位。

备注:内容仅供参考。