支付宝未扫码查询返回ACQ.TRADE_NOT_EXIST如何处理
结论
正式环境中同样可能出现 ACQ.TRADE_NOT_EXIST。
alipay.trade.precreate 成功返回 qr_code,只表示二维码已经生成,不代表支付宝侧已有可供 alipay.trade.query 查询的交易记录。用户还未扫码时,查询接口可能返回 ACQ.TRADE_NOT_EXIST。扫码后,交易状态通常会进入 WAIT_BUYER_PAY、TRADE_SUCCESS 等状态,此时才能查询到相应记录。
所以,不要把未扫码阶段出现的 ACQ.TRADE_NOT_EXIST 一律当作系统异常反复抛出。可以将它转换成“等待用户扫码”的业务状态,并根据轮询次数、退避间隔和订单有效期决定是否继续查询。
为什么生成二维码后仍然查不到交易
线下扫码支付通常分为两个阶段:
- 商户调用
alipay.trade.precreate,支付宝返回二维码内容。 - 用户使用支付宝扫码后,支付宝侧开始建立或推进实际交易。
在第一个阶段,商户自己的系统中已经有订单,但支付宝交易记录可能还没有进入查询接口可见的状态。这意味着:
alipay.trade.precreate返回成功,不代表订单已经支付。- 用户尚未扫码时返回
ACQ.TRADE_NOT_EXIST,不一定是预下单失败。 - 扫码后可能先查到
WAIT_BUYER_PAY,完成付款后再变为TRADE_SUCCESS。 - 支付结果主要通过支付宝异步通知确认,主动查询一般用于页面展示和补偿。
沙箱与正式环境的处理时序可能不同,不能认为每次预下单后的查询结果都会完全一致。业务代码需要同时兼容“查不到交易”和“等待付款”这两种未支付状态。
推荐处理方案
1. 先保存预下单结果
调用 alipay.trade.precreate 成功后,在本地保存:
- 商户订单号
out_trade_no - 二维码内容
qr_code - 本地订单状态,例如
WAIT_SCAN - 创建时间和失效时间
- 支付金额
查询接口暂时返回 ACQ.TRADE_NOT_EXIST 时,不要立即把本地订单标记为失败。
2. 将 ACQ.TRADE_NOT_EXIST 转换为业务状态
可以按下表对查询结果进行分类:
| 查询结果 | 建议处理 |
|---|---|
TRADE_SUCCESS | 标记支付成功,同时做好异步通知的幂等处理 |
TRADE_FINISHED | 标记交易完成 |
WAIT_BUYER_PAY | 保持等待支付状态 |
TRADE_CLOSED | 标记订单关闭 |
ACQ.TRADE_NOT_EXIST | 订单仍在有效期内时,视为等待扫码 |
| 网络超时、网关异常 | 记录为查询状态未知,稍后重试 |
| 参数错误、签名错误、权限错误 | 立即告警并检查配置,不要持续重试 |
只有满足以下条件,才适合把 ACQ.TRADE_NOT_EXIST 当作“等待扫码”处理:
alipay.trade.precreate已明确成功;- 查询使用的
out_trade_no与预下单时完全一致; - 应用、支付宝环境和商户配置没有切换;
- 本地订单仍处于二维码有效期内。
如果有任一条件不成立,应先排查参数或环境配置。
3. 使用有限轮询和退避策略
前端或服务端都可以轮询订单状态,但不要无限、高频地调用支付宝接口。例如:
- 开始时每 2~3 秒查询一次;
- 多次查询仍未扫码后,逐步延长到 5~10 秒;
- 达到业务设定的二维码有效期后停止查询;
- 页面关闭或用户取消支付后停止轮询;
- 订单超时后,按业务规则关闭订单或重新生成二维码。
具体的轮询频率和有效期,应根据所接入产品的官方文档、接口限流要求和自身业务配置确定。
4. 以异步通知为主,主动查询为辅
支付成功后,应通过支付宝异步通知更新订单状态,并完成以下校验:
- 验证签名;
- 校验
app_id; - 校验
out_trade_no; - 校验支付金额和币种;
- 校验收款方信息;
- 保证订单状态更新幂等。
主动查询主要用于:
- 在收银台页面显示支付进度;
- 异步通知延迟时进行补偿;
- 定时修复长时间停留在处理中状态的订单。
Java 处理示例
下面的示例主要演示响应分类逻辑。具体的 SDK 类名和异常类型,应以项目实际使用的支付宝 Java SDK 版本为准。
public enum PaymentQueryStatus {
WAIT_SCAN,
WAIT_PAY,
SUCCESS,
CLOSED,
UNKNOWN,
ERROR
}
public PaymentQueryStatus queryPayment(
AlipayClient alipayClient,
String outTradeNo) throws AlipayApiException {
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent(
"{\"out_trade_no\":\"" + outTradeNo + "\"}"
);
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
String tradeStatus = response.getTradeStatus();
if ("TRADE_SUCCESS".equals(tradeStatus)
|| "TRADE_FINISHED".equals(tradeStatus)) {
return PaymentQueryStatus.SUCCESS;
}
if ("WAIT_BUYER_PAY".equals(tradeStatus)) {
return PaymentQueryStatus.WAIT_PAY;
}
if ("TRADE_CLOSED".equals(tradeStatus)) {
return PaymentQueryStatus.CLOSED;
}
return PaymentQueryStatus.UNKNOWN;
}
String subCode = response.getSubCode();
if ("ACQ.TRADE_NOT_EXIST".equals(subCode)) {
// 前提:预下单已经成功,订单号和运行环境均确认无误。
// 此时不抛异常,交给上层按照订单有效期决定是否继续轮询。
return PaymentQueryStatus.WAIT_SCAN;
}
// 其他业务错误不能统一按“未扫码”处理。
throw new IllegalStateException(
"Alipay trade query failed: code=" + response.getCode()
+ ", msg=" + response.getMsg()
+ ", subCode=" + response.getSubCode()
+ ", subMsg=" + response.getSubMsg()
);
}
业务层还需要结合本地订单状态和有效期进行判断:
public PaymentQueryStatus getOrderStatus(LocalOrder order)
throws AlipayApiException {
if (order.isPaid()) {
return PaymentQueryStatus.SUCCESS;
}
if (order.isClosed() || order.isExpired()) {
return PaymentQueryStatus.CLOSED;
}
PaymentQueryStatus status =
queryPayment(alipayClient, order.getOutTradeNo());
if (status == PaymentQueryStatus.SUCCESS) {
// 更新时必须使用条件更新或事务锁,避免查询与异步通知重复入账。
markOrderPaidIdempotently(order.getOutTradeNo());
}
return status;
}
避免日志中反复出现异常
如果当前代码对所有 response.isSuccess() == false 的情况都直接抛出异常,可以单独处理 ACQ.TRADE_NOT_EXIST,并调整日志级别:
if ("ACQ.TRADE_NOT_EXIST".equals(response.getSubCode())) {
log.debug(
"Alipay trade is not visible yet, outTradeNo={}",
outTradeNo
);
return PaymentQueryStatus.WAIT_SCAN;
}
等待扫码属于正常业务状态,可以记录为 DEBUG,也可以只记录指标,不必每次都打印日志。其他错误仍应保留 WARN 或 ERROR,以免掩盖真正的签名、权限或参数问题。
注意事项
不要对所有 ACQ.TRADE_NOT_EXIST 都无限重试。如果订单长时间查询不到,需要检查:
- 查询时使用的是否为预下单时的同一个
out_trade_no; - 是否误用了尚未返回的
trade_no; - 预下单和查询是否使用同一个
app_id; - 是否混用了沙箱网关和正式网关;
- 商户私钥、支付宝公钥及应用配置是否与当前环境对应;
- 预下单是否真正成功,而不只是 HTTP 请求成功;
- 二维码是否已经超过业务设定的有效期。
本地订单可以划分为 WAIT_SCAN、WAIT_PAY、PAID、CLOSED 和 QUERY_UNKNOWN。这样处理后,ACQ.TRADE_NOT_EXIST 只是支付流程中一个可预期的状态,不会再被当作需要反复抛出的程序异常。
备注:内容仅供参考。
