alipay.trade.close 返回 ACQ.TRADE_NOT_EXIST,如何阻止订单支付?
结论
ACQ.TRADE_NOT_EXIST 表示支付宝当前找不到与该 out_trade_no 对应的交易,所以 alipay.trade.close 没有可关闭的对象。
用户已经打开支付宝收银台,不代表支付宝已经生成了可查询、可关闭的交易。在部分支付流程中,支付链接、收银台会话与正式交易记录并不是同时创建的。因此,立即调用 alipay.trade.close 不一定能阻止用户付款。
建议按以下方式处理:
- 先在商户系统中将订单设为“已取消”或“禁止支付”。
- 查询支付宝交易。只有交易存在且状态为
WAIT_BUYER_PAY时,才调用alipay.trade.close。 - 如果返回
ACQ.TRADE_NOT_EXIST,在短时间内延迟重查,避免遗漏稍后才创建的交易。 - 如果支付已经完成,则不能再关闭交易,只能按业务规则调用退款接口。
- 创建支付请求时设置合理的支付有效期,缩短旧支付入口可继续使用的时间。
为什么查询和关闭都提示交易不存在
out_trade_no 是商户生成的订单号。拥有这个编号,不代表支付宝已经创建了对应的交易记录。
支付过程中可能涉及几个不同的对象:
- 商户系统中的业务订单;
- 跳转支付宝时使用的支付请求或收银台会话;
- 支付宝正式建立的交易记录。
即使用户已经进入收银台,甚至停留在输入密码的页面,支付宝是否已建立交易记录,仍取决于具体支付产品和当前支付阶段。如果交易尚未落库,以下两个接口都会返回交易不存在:
alipay.trade.queryalipay.trade.close
还需要排查这些常见问题:
- 查询和发起支付使用了不同的
app_id; - 正式环境与沙箱环境混用;
out_trade_no传错,或者重新生成支付请求时更换了订单号;- 支付请求并未成功提交到支付宝;
- 服务商模式下使用了不同的商户授权上下文;
- 支付刚发起便立即查询,交易数据还不能被查询。
因此,ACQ.TRADE_NOT_EXIST 不能直接理解为“用户绝对无法继续支付”。它只表示在本次查询使用的应用、商户和环境下,暂时找不到这笔交易。
推荐的处理流程
1. 先关闭商户自己的订单
业务上决定不再允许支付时,应先原子地修改本地订单状态,例如:
PAYING -> CANCELED
后续创建支付请求、处理支付结果和执行业务履约时,都必须检查该状态。不能只根据支付宝接口的调用结果判断本地订单是否有效。
如果用户再次请求支付,本地接口应直接拒绝:
if (order.getStatus() == OrderStatus.CANCELED) {
throw new IllegalStateException("订单已取消,不能继续支付");
}
这样可以防止用户从商户页面重新获取支付链接,但不能保证已经打开的支付宝收银台立即失效。
2. 查询支付宝交易状态
查询时,应使用与发起支付时完全相同的应用、环境和商户授权信息:
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001"
}
""");
AlipayTradeQueryResponse response = alipayClient.execute(request);
根据查询结果分别处理:
| 查询结果 | 处理方式 |
|---|---|
WAIT_BUYER_PAY | 调用 alipay.trade.close |
TRADE_SUCCESS | 已支付,不能关闭;根据业务决定是否退款 |
TRADE_FINISHED | 交易已完成,通常不能关闭 |
TRADE_CLOSED | 无需重复关闭 |
ACQ.TRADE_NOT_EXIST | 本地保持取消状态,并进行有限次数的延迟重查 |
3. 交易存在后再关闭
查询到待付款交易后,关闭操作才有明确的对象:
AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
request.setBizContent("""
{
"out_trade_no": "ORDER_202609090001"
}
""");
AlipayTradeCloseResponse response = alipayClient.execute(request);
if (!response.isSuccess()) {
String subCode = response.getSubCode();
String subMsg = response.getSubMsg();
// 记录结果,并根据 subCode 判断是否需要重新查询交易状态
}
如果已经取得支付宝交易号,也可以传入 trade_no。无论使用哪个编号,都要确认它属于当前应用和商户上下文。
关闭成功后,建议再次查询,确认最终状态已经变为 TRADE_CLOSED。关闭逻辑还应支持重复执行,避免任务重试引发异常。
4. 对交易不存在进行延迟重查
如果用户可能仍停留在已经打开的收银台中,支付宝交易可能稍后才会创建。可以为此设置一个范围明确的补偿任务,例如:
public void cancelPayment(String outTradeNo) {
// 第一步:原子地取消本地订单
orderService.cancelIfUnpaid(outTradeNo);
// 第二步:异步检查支付宝交易
paymentCloseTask.schedule(outTradeNo);
}
public void checkAndClose(String outTradeNo) {
TradeQueryResult result = alipayGateway.query(outTradeNo);
switch (result.status()) {
case WAIT_BUYER_PAY:
alipayGateway.close(outTradeNo);
break;
case TRADE_SUCCESS:
refundService.createRefundIfRequired(outTradeNo);
break;
case TRADE_FINISHED:
case TRADE_CLOSED:
break;
case NOT_EXIST:
// 在限定时间和次数内延迟重试,不能无限轮询
paymentCloseTask.retryLater(outTradeNo);
break;
}
}
重查间隔、次数和持续时间应根据实际支付产品、订单有效期及业务风险确定,不能假设所有场景都适用同一个固定值。
已经支付成功时如何处理
查询与关闭之间存在竞态。例如:
- 系统查询到
WAIT_BUYER_PAY; - 用户完成密码验证;
- 系统随后调用关闭接口;
- 由于交易已经支付成功,关闭失败。
即使先查询再关闭,也无法保证一定能赶在用户付款前完成。收到支付成功通知后,系统必须重新检查本地订单状态:
if (localOrder.isCanceled() && alipayTrade.isPaid()) {
// 不进行履约,并按业务规则发起退款或进入人工处理
refundService.createRefundIfRequired(localOrder, alipayTrade);
}
退款通常使用 alipay.trade.refund。是否自动退款、退多少以及是否需要人工审核,应按具体业务规则处理。
处理异步通知时还应做到:
- 验证支付宝通知签名;
- 校验
app_id、商户身份、订单号和金额; - 通过数据库事务或状态机避免重复履约;
- 保证关闭、退款和通知处理的幂等性;
- 即使本地订单已经取消,也不能直接忽略支付成功通知。
从支付请求阶段限制有效期
如果使用的支付接口支持支付超时参数,可以在创建支付请求时设置合理的有效期,例如该接口支持的 timeout_express 或 time_expire。
可用参数、格式和限制取决于具体支付产品及当前接口文档,不能把一个支付接口的配置直接套用到另一个接口。设置超时时间后,仍要保留本地取消、交易关闭和支付后退款等补偿逻辑。超时配置不能代替完整的订单状态管理。
注意事项
alipay.trade.close只能关闭已经存在且允许关闭的支付宝交易,不能关闭单纯的商户订单号或收银台页面。- 取消本地订单不会自动关闭支付宝交易,关闭支付宝交易也不会自动修改本地业务状态。
- 不要无限重试
ACQ.TRADE_NOT_EXIST。应设置重试期限,并记录最终处理结果。 - 未确认交易状态前不要直接退款。退款接口要求交易已经支付成功。
- 不能只根据同步返回判断支付是否成功。最终状态应结合主动查询和通过验签的异步通知来确认。
- 如果应用、环境、订单号和商户授权上下文确认完全一致,但经过较长时间仍查询不到交易,需要结合支付接口的原始请求、响应和支付宝网关日志继续排查。
备注:内容仅供参考。
