PAYATHON 2026

alipay.trade.close 与 app支付接口2.0 状态问题

支付老李

结论

  1. 用户还没有输入密码时,通常可以关闭交易。
    只要支付宝服务端的交易状态仍是 WAIT_BUYER_PAY,调用 alipay.trade.close 一般可以成功。用户是否停留在收银台或已经进入密码输入页面,都不能作为判断依据,最终要看支付宝服务端记录的交易状态。

  2. 交易已经支付成功时,关闭通常会失败。
    如果交易状态已经变为 TRADE_SUCCESS 或 TRADE_FINISHED,就不能再通过 alipay.trade.close 撤销支付。需要退回资金时,应根据业务情况调用退款接口。

  3. 用户输入密码后,商户系统不一定能立即确认交易结果。
    输入密码不代表商户端已经收到最终支付结果。网络延迟、客户端中断、异步通知延迟或支付仍在处理中,都可能让商户暂时无法确认结果。最终应以支付宝服务端查询结果和异步通知为准,不能只看 App 返回的 resultStatus。

为什么会出现这些情况

alipay.trade.app.pay、alipay.trade.close 和支付宝的实际支付处理可能同时进行,因此会产生状态竞争:

创建交易
  ↓
WAIT_BUYER_PAY
  ├─ 商户先关闭成功 → TRADE_CLOSED
  └─ 支付先完成     → TRADE_SUCCESS

例如,用户已经输入密码,但支付宝还没有完成支付处理。此时商户发起关闭请求,可能出现以下结果:

  • 关闭操作先成功,交易进入 TRADE_CLOSED,之后无法再完成支付;
  • 支付操作先成功,交易进入 TRADE_SUCCESS,关闭请求失败;
  • 调用过程中发生超时或通信异常,仅凭这次接口响应无法确定最终状态,必须重新查询。

所以,不能根据用户是否输入密码来判断交易能否关闭,也不能把关闭请求报错直接当成已经支付成功。请求参数错误、交易不存在、权限问题或当前交易状态不允许关闭,也可能导致关闭失败。排查时应检查返回的 code、sub_code 和 sub_msg。

推荐处理流程

1. 使用同一个商户订单号

调用 alipay.trade.app.pay 时生成唯一的 out_trade_no,后续查询、关闭和退款都通过这个订单号关联本地交易。

本地订单至少应区分以下状态:

  • 待支付;
  • 支付处理中;
  • 支付成功;
  • 已关闭;
  • 退款中或已退款。

App 退出、支付页面关闭或客户端返回失败,都不足以证明订单尚未支付,不能直接把订单改成未支付状态。

2. 关闭前先查询交易

准备关闭超时订单时,先调用 alipay.trade.query,再根据查询结果处理:

  • WAIT_BUYER_PAY:可以尝试调用 alipay.trade.close;
  • TRADE_SUCCESS:按支付成功处理,不再关闭;
  • TRADE_FINISHED:按支付完成处理,不再关闭;
  • TRADE_CLOSED:把本地订单更新为已关闭;
  • 查询不到交易或状态无法确定:不要直接认定关闭成功,应结合重试、异步通知和人工补偿机制继续处理。

查询可以缩小状态竞争的时间窗口,但无法完全避免并发。因此,调用关闭接口后仍需检查返回结果,必要时再次查询。

3. 根据关闭结果更新订单

只有支付宝明确返回关闭成功,才能把本地订单更新为已关闭。

如果关闭失败,应按以下步骤处理:

  1. 记录完整的接口返回码和支付宝交易号;
  2. 再次调用 alipay.trade.query;
  3. 如果查询结果为支付成功,执行支付成功逻辑;
  4. 如果交易仍处于待支付状态,根据失败原因决定是否重试关闭;
  5. 如果查询结果仍不明确,保留支付处理中状态,等待异步通知或后续补偿查询。

4. 以服务端结果确认支付

App 端的同步返回主要用于改善用户体验,不应作为最终入账依据。服务端需要同时处理:

  • 支付宝异步通知;
  • alipay.trade.query 主动查询;
  • 定时补偿任务。

处理异步通知时,必须验证签名,并核对以下信息:

  • app_id;
  • out_trade_no;
  • 订单金额;
  • 收款方信息;
  • 本地订单是否已经处理。

支付成功逻辑必须具备幂等性,避免异步通知和主动查询同时到达时重复发货或重复记账。

Java 处理示例

下面的示例只展示核心判断逻辑。具体异常类型、SDK 初始化方式和日志实现,应以项目实际使用的支付宝 SDK 为准。

public void closeOrderSafely(String outTradeNo) throws AlipayApiException {
    // 1. 先查询支付宝侧状态
    AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
    queryRequest.setBizContent(
        "{\"out_trade_no\":\"" + outTradeNo + "\"}"
    );

    AlipayTradeQueryResponse queryResponse = alipayClient.execute(queryRequest);

    if (!queryResponse.isSuccess()) {
        // 查询失败不代表未支付,保留处理中状态并安排补偿查询
        throw new IllegalStateException(
            "Query failed, code=" + queryResponse.getCode()
                + ", subCode=" + queryResponse.getSubCode()
                + ", subMsg=" + queryResponse.getSubMsg()
        );
    }

    String tradeStatus = queryResponse.getTradeStatus();

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        markOrderPaidIdempotently(
            outTradeNo,
            queryResponse.getTradeNo()
        );
        return;
    }

    if ("TRADE_CLOSED".equals(tradeStatus)) {
        markOrderClosedIdempotently(outTradeNo);
        return;
    }

    if (!"WAIT_BUYER_PAY".equals(tradeStatus)) {
        // 未识别或处于暂不能关闭的状态,暂不修改为已关闭
        scheduleQueryRetry(outTradeNo);
        return;
    }

    // 2. 交易待支付,尝试关闭
    AlipayTradeCloseRequest closeRequest = new AlipayTradeCloseRequest();
    closeRequest.setBizContent(
        "{\"out_trade_no\":\"" + outTradeNo + "\"}"
    );

    AlipayTradeCloseResponse closeResponse =
        alipayClient.execute(closeRequest);

    if (closeResponse.isSuccess()) {
        markOrderClosedIdempotently(outTradeNo);
        return;
    }

    // 3. 关闭失败可能是并发支付导致,再查询一次最终状态
    logCloseFailure(
        outTradeNo,
        closeResponse.getCode(),
        closeResponse.getSubCode(),
        closeResponse.getSubMsg()
    );

    reconcileOrderByQuery(outTradeNo);
}

实际项目中不要通过字符串拼接生成 JSON。这里采用这种写法只是为了突出处理流程,正式代码应使用 SDK 提供的业务模型或可靠的 JSON 序列化组件。

异步通知示例

public String handleAlipayNotify(
        Map<String, String> params) throws AlipayApiException {

    boolean verified = AlipaySignature.rsaCheckV1(
        params,
        alipayPublicKey,
        "UTF-8",
        "RSA2"
    );

    if (!verified) {
        return "failure";
    }

    String outTradeNo = params.get("out_trade_no");
    String tradeNo = params.get("trade_no");
    String tradeStatus = params.get("trade_status");
    String totalAmount = params.get("total_amount");

    // 还应校验 app_id、金额、收款方等是否与本地订单一致
    validateNotify(outTradeNo, totalAmount, params);

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        // 必须幂等,避免重复记账或重复发货
        markOrderPaidIdempotently(outTradeNo, tradeNo);
    }

    return "success";
}

注意事项

  • alipay.trade.close 只能关闭尚未支付的交易,不能用于退款。
  • 如果交易已经扣款且状态为 TRADE_SUCCESS,应先核对订单和资金状态,再按照业务规则退款。
  • 接口超时、网络断开或没有收到响应,都表示结果未知,不能直接判定操作失败。
  • App 返回取消、失败或未知状态,不能证明支付宝侧没有扣款。
  • 主动查询和异步通知可能乱序到达。本地状态更新必须保持幂等,同时防止后到的关闭结果覆盖已经成功支付的订单状态。
  • 自动关单应给用户留出合理的支付时间,并在关单前后核对交易状态,尽量减少用户付款过程中发生的状态竞争。

备注:内容仅供参考。