PAYATHON 2026

支付宝扫码付未支付订单如何查询、关闭并拒绝超时支付?

支付小周

结论

要让支付宝拒绝超时后的付款,仅在商户系统中把订单标记为”已超时”还不够,还需要控制支付宝侧的交易状态:

  1. 创建二维码交易时,通过 timeout_express 设置支付宝侧的交易有效期。
  2. 商户订单到期后,调用 alipay.trade.close 主动关闭支付宝交易。
  3. 关单前后调用 alipay.trade.query 查询最终状态,同时处理关单与支付并发发生的情况。
  4. 如果支付宝已经返回支付成功,关单无法撤销这笔支付。业务无法接受超时付款时,只能调用 alipay.trade.refund 退款。

所谓”未支付就查询不到订单”,通常不代表支付宝没有生成交易。只要 alipay.trade.precreate 成功返回二维码,交易一般已经进入支付宝系统,未付款时通常处于 WAIT_BUYER_PAY 状态。如果查询结果显示交易不存在,应检查查询参数、应用和商户身份是否一致,也要考虑短暂的数据同步延迟。

创建交易时设置有效期

扫码支付通常使用 alipay.trade.precreate 创建交易。创建时建议传入 timeout_express,让支付宝侧明确交易的失效时间,不能只依赖商户系统自己的倒计时。

{
  "out_trade_no": "ORDER_20260909_0001",
  "total_amount": "99.00",
  "subject": "商品订单",
  "timeout_express": "10m"
}

timeout_express 支持的格式、最小值和最大值可能因接口产品及签约能力而异,应以支付宝开放平台当前的对应接口文档为准,不要自行假定任意秒数都有效。

设置该参数后,商户系统仍应保存自己的过期时间,例如:

expire_at = created_at + 10 minutes

业务判断应以服务端时间为准,不能依赖扫码页面中的 JavaScript 倒计时。

查询未支付交易

使用商户订单号查询时,需要传入创建交易时使用的同一个 out_trade_no:

{
  "out_trade_no": "ORDER_20260909_0001"
}

查询接口为:

alipay.trade.query

需要处理的交易状态主要包括:

  • WAIT_BUYER_PAY:交易已创建,正在等待用户付款。
  • TRADE_SUCCESS:付款成功。
  • TRADE_FINISHED:交易已完成,通常不可退款,具体以接口规则为准。
  • TRADE_CLOSED:交易已关闭,无法继续付款。

如果查询返回类似 ACQ.TRADE_NOT_EXIST 的结果,可以依次检查:

  1. out_trade_no 是否与预创建时完全一致。
  2. 查询请求是否使用了同一个 app_id、商户账号和签约主体。
  3. alipay.trade.precreate 是否真正执行成功,而非只在商户本地生成了订单。
  4. 是否在预创建成功后立即发起了查询。必要时可以短暂重试,但不能无限重试。
  5. 是否混用了商户订单号、支付宝交易号 trade_no 或其他流水号。

如果预创建请求失败,支付宝侧就没有可关闭的交易。此时只需关闭商户本地订单,并确保页面不再展示或使用原二维码。

主动关闭订单

订单到期后,调用:

alipay.trade.close

使用商户订单号关单的请求参数示例:

{
  "out_trade_no": "ORDER_20260909_0001"
}

如果已经取得支付宝交易号,也可以使用 trade_no。实际实现时,应保存每次预创建请求的结果、out_trade_no、二维码内容,以及后续查询得到的 trade_no。

下面是一个简化的 Java SDK 示例。具体类名和调用方式需要根据项目使用的支付宝 SDK 版本核对:

AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
request.setBizContent("""
{
  "out_trade_no": "ORDER_20260909_0001"
}
""");

AlipayTradeCloseResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    // 关单请求成功,随后仍应查询一次最终交易状态
} else {
    // 记录 code、sub_code、msg、sub_msg,按错误类型处理
}

关单接口返回失败时,不能直接把支付宝交易视为已关闭。失败可能是因为交易已经支付、正在处理或已经关闭,也可能是请求参数或商户身份不匹配。无论哪种情况,都必须再通过 alipay.trade.query 确认状态。

处理”最后几秒付款”的竞态

用户扫码、输入密码和支付宝完成扣款涉及多个步骤。商户的超时任务可能与支付宝支付请求同时发生,因此会出现以下竞态:

用户提交支付 ───────┐
                   ├── 支付先成功:关单不能撤销支付
商户执行关单 ───────┘
                   └── 关单先成功:后续支付应被拒绝

建议使用明确的订单状态机:

PENDING -> PAID
PENDING -> CLOSING -> CLOSED
PAID -> REFUNDING -> REFUNDED

超时处理可以采用以下逻辑:

void expireOrder(String outTradeNo) {
    // 使用数据库条件更新或分布式锁,防止支付通知与超时任务同时改状态
    MerchantOrder order = orderRepository.findByOutTradeNo(outTradeNo);

    if (!order.isPending() || !order.isExpired()) {
        return;
    }

    TradeStatus status = queryAlipay(outTradeNo);

    if (status == TradeStatus.TRADE_SUCCESS
            || status == TradeStatus.TRADE_FINISHED) {
        markPaidAtomically(outTradeNo);
        handleLatePayment(outTradeNo);
        return;
    }

    if (status == TradeStatus.TRADE_CLOSED) {
        markClosedAtomically(outTradeNo);
        return;
    }

    CloseResult closeResult = closeAlipay(outTradeNo);

    // 无论关单调用结果如何,都再次查询最终状态
    TradeStatus finalStatus = queryAlipayWithLimitedRetry(outTradeNo);

    if (finalStatus == TradeStatus.TRADE_CLOSED) {
        markClosedAtomically(outTradeNo);
    } else if (finalStatus == TradeStatus.TRADE_SUCCESS
            || finalStatus == TradeStatus.TRADE_FINISHED) {
        markPaidAtomically(outTradeNo);
        handleLatePayment(outTradeNo);
    } else {
        markForManualOrScheduledReconciliation(outTradeNo, closeResult);
    }
}

handleLatePayment 应按照业务规则处理。如果订单超时后绝对不能履约,应在确认支付成功后调用 alipay.trade.refund。这种处理属于”支付成功后退款”,无法在原支付链路中拒绝付款。

支付通知也要检查订单状态

不能只依赖主动查询。收到支付宝异步通知后,应先验签,再核对以下信息:

  • app_id
  • 商户身份信息
  • out_trade_no
  • trade_no
  • total_amount
  • 交易状态

如果本地订单已经进入 CLOSING 或 CLOSED,但通知显示交易成功,不能直接丢弃通知。应将其记录为”超时支付”,再次查询支付宝的最终状态,再根据业务规则履约或退款。

更新订单状态时,必须使用数据库事务、条件更新或其他并发控制。例如,只有当前状态仍为 PENDING,才能更新为 PAID 或 CLOSING,以免关单任务和支付通知相互覆盖。

实施时的注意事项

  • out_trade_no 应全局唯一。订单关闭后,不要复用原订单号创建新交易。
  • 二维码过期后,页面应立即将其设为失效状态,停止轮询,也不要继续展示旧二维码。
  • 页面倒计时结束不代表支付宝交易已经关闭。
  • 关单接口调用成功也不等于本地处理完成,之后仍需查询和对账。
  • 查询重试应限制次数并设置间隔。状态持续不明确的订单,应进入定时补偿或人工核对队列。
  • 异步通知、主动查询和账单对账应组成三层确认机制。
  • 如果业务要求严格拒绝超时付款,应为关单任务预留提前量。例如,页面展示的有效期为 10 分钟,可以提前停止使用二维码并开始关单。具体提前多久,需要根据实际接口耗时和业务容忍度确定。
  • 即使提前关单,也无法在技术上消除所有并发窗口。因此,系统仍需准备”支付已成功但商户订单已过期”时的退款补偿流程。

备注:内容仅供参考。