统一收单交易查询中out_trade_no查询不到订单
结论
trade_no 是支付平台生成的全局交易号,而 out_trade_no 是商户自定义订单号,其查询通常受创建订单时的应用、商户身份和环境约束。
因此,出现“trade_no 能查到,out_trade_no 查不到”,通常不是订单状态问题,而是以下两类原因之一:
- 查询请求使用的
app_id、商户账号、授权身份或沙箱环境与创建订单时不一致。 - 查询时实际发送的
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_noout_trade_no- 返回的
code、sub_code和sub_msg - SDK 实际发送的业务参数
提交问题时不要公开应用私钥、签名原文中的敏感信息或完整授权令牌。
注意事项
trade_no查询成功,说明交易本身存在且支付状态可被读取。- 响应中的
out_trade_no是订单创建时记录的值,不代表当前查询请求中的同名参数已成功匹配。 - 商户订单号应在约定的商户或应用范围内保持唯一,不要在不同支付重试中随意复用。
- 沙箱行为不能直接证明生产环境一定相同。若所有参数完全一致后仍只有沙箱复现,也可能是沙箱数据或路由异常,但在没有错误码和完整请求日志的情况下,不能直接认定为平台故障。
- 最稳妥的处理方式是同时保存
out_trade_no与支付成功后返回的trade_no:日常业务按out_trade_no关联,异常排查时使用trade_no精确定位。
备注:内容仅供参考。
