PAYATHON 2026

APP支付生成orderString后取消支付,如何关闭交易?

支付阿杰

结论

orderString 并不表示支付宝交易已经创建成功。它只是经过签名的 APP 支付请求。在用户通过支付宝客户端提交请求之前,支付宝侧可能没有对应的交易记录,此时调用 Factory.Payment.Common().close() 就会返回“交易不存在”。

已经生成的 orderString 不能通过关闭接口直接撤销。建议按以下方式处理:

  1. 生成 orderString 时设置较短的支付有效期。
  2. 用户取消支付后,先把商户订单标记为已取消。
  3. 继续查询支付宝交易。交易创建后,如果仍处于待支付状态,再调用关闭接口。
  4. 如果交易因并发操作已经支付成功,则无法关闭,需要按业务规则退款。

旧的 orderString 即使还能调起支付宝客户端,也不一定能完成付款。最终是否允许支付,由支付宝侧的交易状态和支付有效期决定。

为什么会提示“交易不存在”

Factory.Payment.App().pay() 主要负责组装参数并签名,生成供 APP 调用支付宝客户端的 orderString。这一步不一定会立即在支付宝服务端创建交易。

通常,APP 需要先把 orderString 交给支付宝客户端,再由客户端向支付宝发起请求。支付宝收到请求后,才会创建对应的交易。因此,实际流程中可能存在以下时间差:

商户服务端生成 orderString
        ↓
用户尚未提交,或直接取消
        ↓
支付宝侧没有对应交易
        ↓
调用 close() 返回交易不存在

关闭接口只能关闭支付宝侧已经存在的交易,不能撤销本地生成的签名字符串。

推荐处理流程

1. 生成支付请求时设置有效期

可以通过 timeout_express 限制交易的支付时间:

Map<String, String> optionalArgs = new HashMap<>();
optionalArgs.put("timeout_express", "10m");

AlipayTradeAppPayResponse response = Factory.Payment.App()
        .batchOptional(optionalArgs)
        .pay(subject, outTradeNo, totalAmount);

String orderString = response.body;

这里的 10m 只是示例,具体时长应根据实际业务设置。如果当前接口版本支持绝对过期时间参数,也可以使用对应参数,具体以所用 SDK 和支付宝接口文档为准。

支付有效期限制的是最终付款时间,并不保证 orderString 过期后无法调起支付宝客户端。客户端仍可能打开,但用户提交支付时,应因订单过期或交易已关闭而失败。

2. 用户取消后先关闭商户订单

用户点击取消时,应先更新商户系统中的订单状态,不能只依赖支付宝关闭接口的结果:

cancelMerchantOrder(outTradeNo);

后续处理异步通知或查询支付结果时,都必须检查商户订单状态。如果已取消的订单收到支付成功通知,不能直接发货或开通服务,而应转入退款或人工处理流程。

3. 查询交易是否已经在支付宝侧创建

用户取消后,可以查询支付宝交易:

AlipayTradeQueryResponse queryResponse =
        Factory.Payment.Common().query(outTradeNo);

根据实际返回的 trade_status 分别处理:

  • WAIT_BUYER_PAY:交易已经创建,但尚未支付,可以调用关闭接口。
  • TRADE_CLOSED:交易已经关闭,无需再次处理。
  • TRADE_SUCCESS:交易已经支付成功,不能再关闭,需要判断是否退款。
  • TRADE_FINISHED:交易已经结束,通常无法关闭。
  • 查询结果为“交易不存在”:支付宝侧暂时没有该交易,也就没有可关闭的对象。

4. 交易存在后执行关闭

确认交易处于待支付状态后,再调用关闭接口:

AlipayTradeCloseResponse closeResponse =
        Factory.Payment.Common().close(outTradeNo);

if (!closeResponse.isSuccess()) {
    // 记录 subCode、subMsg,并安排后续查询或补偿处理
}

如果第一次查询返回“交易不存在”,可以在支付有效期内安排短时间的延迟查询。例如,用户取消后每隔数秒重试一次,直到订单有效期结束。如果发现交易状态变为 WAIT_BUYER_PAY,应立即关闭。

重试需要设置明确的截止时间和最大次数,不能无限持续。

并发支付的处理

用户取消操作可能与支付宝提交支付同时发生:

用户点击取消 ─────→ 商户订单已取消
支付宝客户端提交 ─→ 支付宝交易创建或支付成功

因此,即使先查询再关闭,也无法彻底消除竞争条件。查询时交易可能仍处于待支付状态,但执行关闭时可能已经付款成功。

最终结果应以支付宝服务端查询和异步通知为准:

switch (tradeStatus) {
    case "WAIT_BUYER_PAY":
        closeTrade(outTradeNo);
        break;
    case "TRADE_SUCCESS":
        refundIfMerchantOrderCancelled(outTradeNo);
        break;
    case "TRADE_CLOSED":
        break;
    default:
        scheduleReconciliation(outTradeNo);
}

退款时需要使用实际支付金额、退款单号和对应的退款接口,不能仅根据客户端的“取消支付”回调直接退款。

注意事项

  • 客户端取消回调不能作为最终交易状态。用户关闭收银台时,交易可能尚未创建,也可能处于待支付状态,甚至可能刚刚支付成功。
  • outTradeNo 必须保持唯一,并用于整个支付、查询、关闭、退款和对账流程。
  • 不要为同一笔业务反复生成长期有效的 orderString,以免延长用户取消后的可支付时间。
  • 商户订单状态更新、交易关闭和退款操作都需要保证幂等。
  • 异步通知必须验签,并根据商户订单状态决定发货、退款或转入异常处理。
  • 如果业务要求用户取消后立即且绝对无法支付,仅靠 APP 支付的 orderString 和关闭接口无法保证。还需要结合较短的支付有效期、商户侧状态控制、延迟关单,以及支付成功后的自动退款机制。

备注:内容仅供参考。