PAYATHON 2026

alipay.trade.pay 同一付款码重复支付结果不一致

支付老李

结论

仅凭这种现象,不能判断支付宝系统出现了异常。

付款码支付中的 auth_code 是一种短时有效的付款凭证,只供单次支付尝试使用。同一个付款码不应拿来创建多笔交易,也不能当作普通的幂等参数反复提交。它与小程序登录场景中的 authCode 并非同一种凭证,但在业务处理上都不能重复消费。

10003 表示交易正在处理中,比如系统正等待用户输入支付密码。收到该状态后,应通过商户订单号查询交易结果,而不是再次调用 alipay.trade.pay,更不能使用同一个 auth_code 创建另一笔订单。

为什么两次调用的结果可能不同

alipay.trade.pay 返回的是调用发生时的交易状态,不一定代表最终结果。

例如:

  1. 第一次请求触发支付流程,用户需要输入密码,接口返回 10003。
  2. 用户随后完成验证,交易状态变为成功。
  3. 商户再次使用相同的付款码发起请求。

两个请求的到达时间、订单号、付款码状态和风控处理阶段可能不同,因此一个请求可能显示成功,另一个仍显示处理中或已经失败。特别是在并发或短时间内重复提交时,只比较两次同步响应,无法确认最终支付结果。

此时需要重点检查两次请求使用的 out_trade_no:

  • 如果 out_trade_no 相同,应按同一笔商户订单处理,并通过查询接口确认最终状态。
  • 如果 out_trade_no 不同,就相当于尝试使用同一个付款码创建两笔交易,不应采用这种方式。

正确的处理流程

1. 每次扫码只提交一次支付请求

收银设备读取付款码后,应立即锁定支付按钮或当前订单状态,防止用户重复点击或系统并发提交。

扫码成功
  ↓
锁定当前订单
  ↓
调用 alipay.trade.pay 一次
  ↓
根据返回状态处理

不要保存付款码用于稍后重试。接口超时后,也不要直接使用同一个付款码重新创建交易。

2. 收到 10003 后查询原订单

10003 并不表示支付已经失败,而是表示交易仍在处理中。商户应保留原来的 out_trade_no,通过 alipay.trade.query 轮询交易状态。

常见状态可以这样处理:

  • TRADE_SUCCESS:支付成功。
  • WAIT_BUYER_PAY:继续等待并查询。
  • 明确失败或交易不存在:根据实际返回码处理。
  • 超过商户设定的等待时间仍未成功:关闭或撤销交易,再提示用户重新出示付款码。

3. 使用商户订单号保证业务幂等

商户系统应使用 out_trade_no 作为支付业务的幂等依据。同一笔订单只能有一个有效的支付处理流程。

public PayResult pay(String outTradeNo, String authCode) {
    Order order = orderRepository.findByOutTradeNo(outTradeNo);

    if (order.isPaid()) {
        return PayResult.success(order.getAlipayTradeNo());
    }

    if (order.isPaying()) {
        return queryTrade(outTradeNo);
    }

    orderRepository.markPaying(outTradeNo);

    AlipayTradePayResponse response = callAlipayTradePay(
        outTradeNo,
        authCode
    );

    if ("10000".equals(response.getCode())) {
        orderRepository.markPaid(
            outTradeNo,
            response.getTradeNo()
        );
        return PayResult.success(response.getTradeNo());
    }

    if ("10003".equals(response.getCode())) {
        return queryTrade(outTradeNo);
    }

    orderRepository.markPayFailed(
        outTradeNo,
        response.getSubCode(),
        response.getSubMsg()
    );
    return PayResult.failed(response.getSubCode());
}

这段代码用于说明状态控制思路。具体的 SDK 类名和字段读取方式,应以项目当前使用的支付宝 SDK 为准。

查询逻辑可以这样组织:

public PayResult queryTrade(String outTradeNo) {
    AlipayTradeQueryResponse response =
        callAlipayTradeQuery(outTradeNo);

    if (!response.isSuccess()) {
        return PayResult.unknown(
            response.getSubCode(),
            response.getSubMsg()
        );
    }

    String tradeStatus = response.getTradeStatus();

    if ("TRADE_SUCCESS".equals(tradeStatus)) {
        orderRepository.markPaid(
            outTradeNo,
            response.getTradeNo()
        );
        return PayResult.success(response.getTradeNo());
    }

    if ("WAIT_BUYER_PAY".equals(tradeStatus)) {
        return PayResult.pending();
    }

    return PayResult.failed(tradeStatus);
}

超时和异常时不要直接重付

如果调用 alipay.trade.pay 时发生网络超时、响应丢失或状态不明确,不能直接生成新的 out_trade_no,再用原付款码提交一次。应按以下顺序处理:

  1. 使用原 out_trade_no 调用 alipay.trade.query。
  2. 查询成功后,更新本地订单状态。
  3. 如果查询结果仍是等待状态,在限定时间内继续轮询。
  4. 如果最终状态仍无法确认,根据接入方案调用撤销或关闭能力。
  5. 确认原交易不会继续成功后,再让用户出示新的付款码。

注意事项

  • 付款码通常会动态刷新,有效期较短,不适合作为可持久化、可重复调用的业务参数。
  • 不要只根据 alipay.trade.pay 的同步响应向用户出货,还应结合最终交易状态和异步通知进行确认。
  • 异步通知同样需要做幂等处理,防止重复通知造成重复发货或重复记账。
  • 如果两次请求使用了不同的 out_trade_no,应分别查询两笔订单,确认是否实际扣款,不能只比较接口返回码。
  • 如果能够稳定复现“一码两笔交易均成功”,应保存两次请求的 out_trade_no、支付宝交易号 trade_no、请求时间和完整响应码,再通过支付宝技术支持渠道核查。不要记录或上传完整付款码等敏感支付凭证。

备注:内容仅供参考。