APP支付退款:商家订单号提示交易不存在
结论
“商家订单号”通常是商户生成的 out_trade_no,“订单号”通常是支付平台生成的 trade_no。两者都能用于退款,但使用 out_trade_no 时,传入值必须与支付请求中的值完全一致。退款请求还必须使用原交易对应的沙箱应用、商户账号和环境配置。
如果使用 trade_no 可以退款,说明这笔交易确实存在。改用 out_trade_no 后提示“交易不存在”,通常是因为商户订单号传错、应用或环境不一致,或者传入了本地业务单号、退款单号等其他编号。
常见原因
1. 传入的不是支付时的 out_trade_no
商户系统中往往同时有多个编号,例如:
- 业务订单号
- 支付订单号
- 商户订单号
out_trade_no - 支付平台交易号
trade_no - 退款请求号
out_request_no
退款接口需要的是支付请求中实际提交的 out_trade_no。不能用 out_request_no、数据库主键或页面展示的其他业务编号代替。
还要确认字符串没有发生变化,例如:
- 前导零被删除;
- 复制时混入空格或换行;
- 大小写被改变;
- 订单号过长,被数据库截断;
- JavaScript 把长数字转换为
Number,造成精度丢失。
订单号应始终按字符串保存和传输。
2. 支付和退款使用了不同的沙箱应用
如果支付时使用应用 A 的 app_id,退款时却加载了应用 B 的配置,仅靠 out_trade_no 可能无法找到原交易。常见情况有:
- 切换过沙箱应用;
- 本地保存了多套配置文件;
- 支付服务和退款服务读取了不同的环境变量;
- 使用了不同的沙箱商户账号;
- 沙箱数据重置后,仍在使用旧订单号。
可以对照支付日志和退款日志,检查其中的 app_id、网关地址、商户账号和密钥配置是否一致。
3. 请求发到了错误的环境
沙箱订单只能在沙箱环境中查询和退款。如果支付请求使用沙箱网关,退款请求却发往正式环境网关,接口会提示交易不存在。
以支付宝为例,沙箱请求通常使用以下网关:
https://openapi-sandbox.dl.alipaydev.com/gateway.do
实际地址应以当前接入平台的最新配置说明为准。正式环境和沙箱环境的配置不能混用。
4. out_trade_no 在业务系统中被重复使用
商户订单号应保持唯一。重复使用同一个 out_trade_no,可能让本地记录无法与平台交易正确对应。在多应用、多商户或反复测试的场景中,这类问题更容易出现。
订单号可以包含环境标识和随机成分,例如:
SBX202609141530001234
排查步骤
- 找到创建 APP 支付订单时的原始请求日志。
- 从日志中复制实际提交的
out_trade_no,不要根据页面展示值或数据库记录推测。 - 找到支付成功响应中返回的
trade_no。 - 确认这两个编号在本地支付记录中对应同一笔交易。
- 对比支付请求和退款请求使用的
app_id、网关地址及商户账号。 - 检查
out_trade_no是否被截断、转换成数字或混入空白字符。 - 调用交易查询接口,验证
out_trade_no能否找到对应交易。 - 如果使用
out_trade_no查询失败,但使用trade_no查询成功,应优先修正订单号映射和环境配置。退款时可以暂时使用trade_no。
日志中不要记录应用私钥、支付密钥或完整的签名材料。
代码示例
下面以支付宝 Java SDK 的典型调用方式为例。不同 SDK 版本中的类名和方法可能有所差异,请以项目实际使用的 SDK 为准。
使用商户订单号退款:
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo("SBX202609141530001234");
model.setRefundAmount("10.00");
model.setOutRequestNo("REFUND202609141530001234");
request.setBizModel(model);
AlipayTradeRefundResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
System.out.println("退款请求成功");
} else {
System.out.println(response.getCode());
System.out.println(response.getSubCode());
System.out.println(response.getSubMsg());
}
如果已经取得支付平台交易号,也可以使用 trade_no:
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setTradeNo("2026091422000000000000000000");
model.setRefundAmount("10.00");
model.setOutRequestNo("REFUND202609141530001234");
request.setBizModel(model);
AlipayTradeRefundResponse response = alipayClient.execute(request);
一次请求最好只使用一种交易定位方式,避免两个字段的内容不一致:
// 二选一
model.setOutTradeNo(originalOutTradeNo);
// model.setTradeNo(originalTradeNo);
注意事项
out_trade_no是原支付订单号,out_request_no是退款请求号,两者用途不同。out_request_no应保持唯一。重复提交相同的退款请求号,通常会被视为同一笔退款请求。- 退款金额不能超过该交易当前的可退金额。
- 不要只看“交易不存在”这段提示,还应记录并核对接口返回的
code、sub_code和sub_msg。 - 沙箱数据不能代表正式环境数据。切换环境后,需要重新创建支付订单。
- 如果原始支付日志中的
out_trade_no仍然无法查询,但同一笔交易的trade_no可以正常退款,应携带脱敏后的请求参数、响应码、app_id和交易时间,请支付平台技术支持协助核查。
备注:内容仅供参考。
