网站支付对接中使用 out_trade_no 查询提示交易不存在
结论
out_trade_no 是商户订单号,trade_no 是支付宝生成的交易号,二者不能混用。
pageExecute 只负责生成跳转到支付宝收银台的 HTML 表单,不会返回 trade_no。浏览器将表单提交到支付宝网关,并由支付宝受理、创建交易后,查询接口才可能查到这笔交易。如果只是调用了 pageExecute,或者用户还没有进入收银台,此时使用 out_trade_no 查询并收到“交易不存在”的结果,通常属于正常情况。
支付完成后,可以从异步通知、同步跳转参数或查询接口结果中取得 trade_no,不能从 pageExecute 返回的表单内容中提取。
原因
网站支付通常要经过以下流程:
- 商户系统生成唯一的
out_trade_no。 - 调用
alipay.trade.page.pay的pageExecute生成 HTML 表单。 - 浏览器提交表单并跳转到支付宝收银台。
- 支付宝受理请求并创建交易。
- 支付完成后,支付宝通过
notify_url发送异步通知。
下面这段代码只会生成等待浏览器提交的页面内容:
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
String form = response.getBody();
此时的 form 通常是一段包含隐藏字段和自动提交脚本的 HTML。它不是支付结果,也不包含可供业务使用的支付宝 trade_no。
如果服务端生成表单后,没有将它响应给浏览器并实际提交到支付宝,支付宝侧就还没有对应的交易。此时使用 out_trade_no 查询,自然会返回“交易不存在”。
正确的处理方式
1. 商户自行生成并保存 out_trade_no
提交支付请求前,先生成唯一的商户订单号,并将它与本地订单绑定。
String outTradeNo = generateUniqueOrderNo();
orderService.saveOrder(outTradeNo, "WAIT_PAY");
在同一个商户应用下,out_trade_no 应当保持唯一。后续进行支付查询、异步通知校验和订单对账时,都要使用这个原始值。
2. 将 pageExecute 返回的表单输出给浏览器
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
model.setOutTradeNo(outTradeNo);
model.setProductCode("FAST_INSTANT_TRADE_PAY");
model.setTotalAmount("0.01");
model.setSubject("测试订单");
request.setBizModel(model);
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
responseHttp.setContentType("text/html;charset=UTF-8");
responseHttp.getWriter().write(response.getBody());
responseHttp.getWriter().flush();
浏览器需要真正执行并提交这段 HTML,进入支付宝收银台。只在服务端打印或保存 response.getBody(),不会在支付宝侧创建交易。
3. 查询时继续使用原始 out_trade_no
AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
AlipayTradeQueryModel queryModel = new AlipayTradeQueryModel();
queryModel.setOutTradeNo(outTradeNo);
queryRequest.setBizModel(queryModel);
AlipayTradeQueryResponse queryResponse =
alipayClient.execute(queryRequest);
if (queryResponse.isSuccess()) {
String tradeNo = queryResponse.getTradeNo();
String tradeStatus = queryResponse.getTradeStatus();
System.out.println("trade_no: " + tradeNo);
System.out.println("trade_status: " + tradeStatus);
} else {
System.out.println("sub_code: " + queryResponse.getSubCode());
System.out.println("sub_msg: " + queryResponse.getSubMsg());
}
如果支付宝已经受理请求并创建交易,但买家还没有付款,查询结果通常会显示待支付状态。具体状态和值应以实际接口返回及当前支付宝接口文档为准。
如果仍然返回“交易不存在”,可以逐项检查:
- 支付表单是否已经提交到支付宝网关。
- 查询使用的
out_trade_no是否与支付请求完全一致。 - 支付请求和查询请求是否使用同一个
app_id。 - 两次请求是否处于相同环境,即都使用沙盒环境或都使用正式环境。
- 两次请求是否使用同一商户身份和正确的签名配置。
- 是否在支付宝受理交易前就立即发起了查询。
4. 从异步通知中获取 trade_no
支付成功后,支付宝会向 notify_url 发送通知。验签通过后,可以读取其中的 trade_no 和 out_trade_no:
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
params.put(entry.getKey(), String.join(",", entry.getValue()));
}
boolean signVerified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey,
"UTF-8",
"RSA2"
);
if (signVerified) {
String outTradeNo = params.get("out_trade_no");
String tradeNo = params.get("trade_no");
String tradeStatus = params.get("trade_status");
// 还需核对 app_id、seller_id、订单金额等业务字段
// 更新订单时应保证幂等
}
实际业务中,应将异步通知作为确认支付结果的主要依据。同步 return_url 主要用于页面跳转和结果展示,不能单独作为订单支付成功的依据。
参数不要混用
按支付宝交易号查询时,应设置 trade_no:
AlipayTradeQueryModel queryModel = new AlipayTradeQueryModel();
queryModel.setTradeNo(alipayTradeNo);
按商户订单号查询时,应设置 out_trade_no:
AlipayTradeQueryModel queryModel = new AlipayTradeQueryModel();
queryModel.setOutTradeNo(merchantOrderNo);
不要把支付宝流水号填入 out_trade_no。即使某次测试看似返回了结果,也不能说明两个字段可以互换。遇到这种情况,应检查代码最终生成的请求参数,以及沙盒页面中各字段的真实含义。
注意事项
pageExecute返回的是收银台跳转表单,不是交易创建结果,因此无法从中可靠获取trade_no。- 用户打开并提交收银台页面后,再根据业务需要查询订单。
- 查询不到交易不一定表示支付失败,也可能是交易请求还没有到达支付宝。
- 本地订单应先保存为待支付状态,不能用查询接口代替本地订单管理。
- 保存支付宝异步通知中的
trade_no,供退款、对账和后续交易查询使用。 - 异步通知可能重复发送,订单更新逻辑必须保证幂等。
- 沙盒环境与正式环境的数据完全隔离,不能跨环境查询。
备注:内容仅供参考。
