PAYATHON 2026

支付宝如何关闭未支付的预支付单并阻止用户继续支付?

支付老李

结论

支付宝交易尚未支付时,不应调用退款接口,而应调用 alipay.trade.close(统一收单交易关闭接口)。

退款接口用于退回已经支付成功的资金。用户还没有付款,也就没有可退金额,此时调用退款接口可能返回 ACQ.TRADE_NOT_EXIST 等错误。系统取消订单后,应使用创建支付时传给支付宝的同一个 out_trade_no 关闭交易。交易关闭成功后,原支付链接、订单字符串或二维码通常无法继续完成支付。

为什么退款接口不适用

这里需要区分三个接口的用途:

  • alipay.trade.close:关闭尚未完成付款的交易。
  • alipay.trade.refund:退回已经支付成功的交易资金。
  • alipay.trade.query:查询交易当前状态。

生成预支付信息后,支付宝侧是否已经形成可查询的交易记录,取决于具体支付产品和交易创建时机,不能简单地认为”只有付款后才会生成支付宝订单”。

可以确定的是,未支付交易没有可退款资金,退款接口也不能用来阻止用户继续支付。

ACQ.TRADE_NOT_EXIST 还可能由以下原因引起:

  • 使用的 out_trade_no 不是创建支付宝交易时传入的商户订单号。
  • 请求发到了不同的支付宝应用或商户账号。
  • 混用了正式环境和沙箱环境。
  • 交易尚未在支付宝侧成功创建。
  • 交易创建与查询、关闭之间存在短暂的状态同步延迟。

正确处理步骤

1. 保存支付宝支付请求中的商户订单号

创建支付时,应保存传给支付宝的 out_trade_no。关闭交易必须使用这个值,它不一定是系统数据库中的主键。

例如:

系统订单 ID:182736
支付宝 out_trade_no:PAY_20260908_182736

关闭时应传:

{
  "out_trade_no": "PAY_20260908_182736"
}

如果支付成功后已经获得支付宝交易号,也可以使用 trade_no,通常选择其中一个即可。

2. 取消本地订单后调用交易关闭接口

Java SDK 示例:

AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();

request.setBizContent("""
{
  "out_trade_no": "PAY_20260908_182736"
}
""");

AlipayTradeCloseResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    // 支付宝交易关闭成功
    // 更新本地支付记录,例如标记为 CLOSED
} else {
    // 记录 code、subCode、subMsg,后续根据交易状态处理
    System.out.println("code: " + response.getCode());
    System.out.println("subCode: " + response.getSubCode());
    System.out.println("subMsg: " + response.getSubMsg());
}

如果直接构造请求,核心业务参数如下:

{
  "out_trade_no": "PAY_20260908_182736"
}

具体网关地址、签名算法和公共请求参数,应以项目正在使用的支付宝 SDK 及支付产品文档为准,不建议自行拼接签名请求。

3. 关闭失败时查询交易状态

关闭交易时,用户可能正在付款,因此不能只根据本地订单状态判断最终结果。关闭失败后,可以调用 alipay.trade.query,确认支付宝侧的实际状态。

常见状态包括:

  • WAIT_BUYER_PAY:等待买家付款,可以继续尝试关闭。
  • TRADE_CLOSED:交易已经关闭。
  • TRADE_SUCCESS:用户已付款,需要进入退款流程。
  • TRADE_FINISHED:交易已结束,能否退款取决于具体产品规则和交易条件。

可以按以下逻辑处理:

取消本地订单
    ↓
调用 alipay.trade.close
    ↓
关闭成功 → 本地支付记录标记为 CLOSED
    ↓
关闭失败 → 调用 alipay.trade.query
    ├─ WAIT_BUYER_PAY → 重试关闭
    ├─ TRADE_CLOSED   → 按关闭成功处理
    └─ TRADE_SUCCESS  → 调用 alipay.trade.refund

必须处理支付与取消并发

需要特别留意竞态问题:系统正在关闭交易时,用户可能恰好完成付款。

即使本地订单已经取消,也必须处理支付宝的异步支付通知。收到支付成功通知后,应先验证签名、商户身份、订单号和金额,再结合本地订单状态进行处理:

if (alipayNotifyVerified) {
    Order order = orderService.findByOutTradeNo(outTradeNo);

    if (order.isCancelled()) {
        // 用户付款与订单取消发生并发
        // 记录支付成功事实,并发起退款或进入人工补偿流程
        refundService.refund(order);
    } else {
        // 正常更新为已支付
        orderService.markPaid(order);
    }
}

不能因为本地订单已经取消,就直接丢弃支付成功通知。否则可能出现用户已经被扣款,但系统既不履约也不退款的情况。

建议增加的保护措施

设置支付有效期

创建支付时,可以查看所用支付宝产品是否支持类似 timeout_express 的超时参数。这个参数可以缩短交易的可支付时间,但不能代替主动关闭:

{
  "out_trade_no": "PAY_20260908_182736",
  "total_amount": "99.00",
  "subject": "订单支付",
  "timeout_express": "15m"
}

参数是否可用以及具体格式,应以对应支付接口的文档为准。

做好幂等控制

订单取消、交易关闭、支付回调和退款都有可能重复执行,因此需要做好幂等控制:

  • 使用 out_trade_no 唯一标识一次支付交易。
  • 关闭接口应支持安全重试。
  • 后到的”取消”操作不能直接覆盖支付成功状态。
  • 退款应使用稳定且唯一的退款请求号。
  • 使用事务、条件更新或状态机控制状态变更。

记录支付宝返回信息

至少保存以下信息,以便定位问题:

  • app_id
  • out_trade_no
  • trade_no
  • 接口名称
  • code
  • sub_code
  • sub_msg
  • 当前使用的是沙箱环境还是正式环境
  • 请求时间及当时的本地订单状态

总结

未支付订单应调用 alipay.trade.close,而不是 alipay.trade.refund。关闭交易时,需要传入创建支付交易所使用的 out_trade_no。如果关闭操作与用户付款同时发生,应查询支付宝侧的交易状态。交易仍未支付时继续关闭,已经支付时则进入退款流程。支付宝异步通知也必须正常处理,确保订单取消后发生的支付能够得到退款或补偿。

备注:内容仅供参考。