PAYATHON 2026

为什么支付宝在线支付没有类似POS支付的冲正交易?

支付老李

结论

支付宝在线支付不会简单地用“退款”代替 POS 冲正。它通常通过交易状态查询、撤销或关闭、退款、异步通知等机制处理异常交易。

两者的区别主要在于:

  • POS 冲正用于处理收单链路超时后,终端无法确认交易结果的问题。
  • 在线支付通常以支付宝保存的交易状态为准,商户通过查询接口和异步通知确认最终结果。
  • 对于状态不明且需要放弃的交易,可以根据产品能力调用 alipay.trade.cancel 或 alipay.trade.close。
  • 对于已经确认支付成功,但业务上需要退回资金的交易,应调用 alipay.trade.refund。这属于退款,不是通信冲正。

不同支付宝产品、接入模式和签约权限所支持的接口并不完全相同。能否使用 alipay.trade.cancel,应以实际产品文档和商户签约能力为准。

为什么在线支付不直接照搬 POS 冲正

POS 冲正与在线交易补偿的目标不同

传统 POS 通信具有明显的终端特征。POS 发出消费请求后,如果收单机构已经记账,但响应没有返回终端,终端就无法判断交易是否成功。

此时发起冲正,是为了处理通信异常造成的状态不确定:

POS 未收到响应
       ↓
发送冲正请求
       ↓
收单端检查原交易
       ├─ 原交易成功:撤销或回退
       └─ 原交易不存在:确认无需处理

在线支付通常可以通过多种方式确认交易结果:

  • 商户订单号可以作为幂等标识。
  • 商户可以主动查询平台侧的交易状态。
  • 支付宝可以通过异步通知补发最终结果。
  • 商户服务端可以定时执行补偿任务。
  • 交易状态不依赖单次同步响应来确定。

因此,HTTP 超时只说明商户没有收到响应,不能据此认定支付失败,也不应马上退款或用新订单号重新发起支付。

冲正不是普通的业务退款

冲正属于原交易链路的技术补偿,用于消除状态不确定,或者回退尚未最终完成的交易。

退款发生在支付成功之后,由商户发起资金返还。它可能有独立的退款单号、退款状态和账务记录,业务含义与冲正不同。

支付接口超时后直接退款,可能造成以下问题:

  1. 原支付实际上还没有成功,退款请求无法处理。
  2. 原支付稍后成功,但此前的退款请求已经失败,最终出现用户已付款、商户未履约的情况。
  3. 商户重新发起支付,导致用户重复付款。
  4. 支付通知和退款处理并发,错误覆盖订单状态。

商户应先确认原交易状态,再决定撤销、关闭或退款。

推荐的处理流程

第一步:为请求设置稳定的商户订单号

同一笔业务交易应始终使用同一个 out_trade_no。网络超时后不要立即更换订单号重新支付,否则可能产生两笔独立交易。

数据库中还应为商户订单号建立唯一约束,例如:

CREATE UNIQUE INDEX uk_payment_out_trade_no
ON payment_order(out_trade_no);

第二步:同步请求超时后,将订单标记为“支付结果未知”

不要把超时直接记为支付失败。可以设置一个独立的中间状态:

PAYING          支付处理中
UNKNOWN         请求超时,最终结果未知
PAID            支付成功
CLOSED          交易关闭
REFUNDING       退款处理中
REFUNDED        已退款

具体状态名称可以自行设计,但必须区分“明确失败”和“暂时无法确定”。

第三步:调用交易查询接口

对于支持统一收单接口的产品,一般可以使用 alipay.trade.query,通过 out_trade_no 或支付宝交易号查询交易状态。

示意请求如下:

{
  "method": "alipay.trade.query",
  "biz_content": {
    "out_trade_no": "ORDER_20260909_0001"
  }
}

查询后应按照接口实际返回的交易状态处理。常见的判断逻辑如下:

switch (tradeStatus) {
    case "TRADE_SUCCESS":
    case "TRADE_FINISHED":
        markOrderPaid();
        break;

    case "WAIT_BUYER_PAY":
        keepWaitingOrCancel();
        break;

    case "TRADE_CLOSED":
        markOrderClosed();
        break;

    default:
        scheduleAnotherQuery();
        break;
}

接口调用成功不等于用户已经付款,还需要检查具体的交易状态。

第四步:根据状态选择撤销、关闭或退款

如果交易仍在等待付款,而业务已经决定不再接受这笔付款,可以调用当前产品支持的关闭或撤销接口。

可能用到的接口包括:

  • alipay.trade.cancel:撤销交易,适用范围和具体行为取决于产品能力。
  • alipay.trade.close:关闭尚未完成的交易。
  • alipay.trade.refund:为已经成功的交易发起退款。

撤销请求示意:

{
  "method": "alipay.trade.cancel",
  "biz_content": {
    "out_trade_no": "ORDER_20260909_0001"
  }
}

关闭请求示意:

{
  "method": "alipay.trade.close",
  "biz_content": {
    "out_trade_no": "ORDER_20260909_0001"
  }
}

已支付交易的退款请求示意:

{
  "method": "alipay.trade.refund",
  "biz_content": {
    "out_trade_no": "ORDER_20260909_0001",
    "refund_amount": "100.00",
    "out_request_no": "REFUND_ORDER_20260909_0001"
  }
}

这些示例只列出了主要业务字段,没有包含网关地址、签名、应用标识、字符集等公共请求参数。实际字段和可用接口应以商户所接入产品的文档为准。

服务端补偿逻辑示例

下面是一段简化的超时补偿逻辑:

public void reconcilePayment(String outTradeNo) {
    PaymentOrder order = paymentOrderRepository.findByOutTradeNo(outTradeNo);

    if (order == null || order.isFinalStatus()) {
        return;
    }

    AlipayTradeQueryResponse response = queryAlipayTrade(outTradeNo);

    if (!response.isSuccess()) {
        scheduleRetry(outTradeNo);
        return;
    }

    String tradeStatus = response.getTradeStatus();

    if ("TRADE_SUCCESS".equals(tradeStatus)
            || "TRADE_FINISHED".equals(tradeStatus)) {
        markPaidIdempotently(
            outTradeNo,
            response.getTradeNo(),
            response.getTotalAmount()
        );
        return;
    }

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

    if ("WAIT_BUYER_PAY".equals(tradeStatus)) {
        if (order.isExpired()) {
            closeOrCancelTrade(outTradeNo);
        } else {
            scheduleRetry(outTradeNo);
        }
        return;
    }

    scheduleRetry(outTradeNo);
}

更新支付成功状态时必须保证幂等,例如使用条件更新:

UPDATE payment_order
SET status = 'PAID',
    alipay_trade_no = :trade_no,
    paid_at = CURRENT_TIMESTAMP
WHERE out_trade_no = :out_trade_no
  AND status IN ('PAYING', 'UNKNOWN');

执行更新后还要检查受影响的行数,避免异步通知、主动查询和补偿任务同时处理同一笔订单,造成重复发货。

异步通知也必须参与最终确认

同步支付响应主要用于改善用户体验,不能作为确认到账的唯一依据。商户还要正确处理支付宝的异步通知:

  1. 验证通知签名。
  2. 校验 app_id、商户订单号、支付宝交易号等标识。
  3. 校验订单金额和币种。
  4. 确认交易状态符合业务要求。
  5. 幂等更新本地订单。
  6. 完成本地持久化后,再返回成功响应。

主动查询和异步通知可能同时到达,因此两条处理路径应复用同一套幂等状态更新逻辑。

为什么不能在超时后立即退款

退款通常要求原交易已经成功。超时后直接调用退款,相当于在支付结果尚未确认时又发起一笔资金操作,无法可靠地消除原交易的不确定性。

更安全的处理顺序如下:

支付请求超时
    ↓
查询原交易
    ├─ 支付成功 → 正常履约;如业务取消,再退款
    ├─ 等待付款 → 继续查询,或按业务规则撤销/关闭
    ├─ 已关闭   → 本地关闭订单
    └─ 仍不明确 → 延迟重试并进入人工对账

只有确认交易成功,同时业务决定不再履约时,才应进入退款流程。

注意事项

不要把 HTTP 超时等同于支付失败

超时可能发生在请求到达支付宝之前,也可能发生在支付宝完成交易之后。商户仅凭通信结果无法判断资金状态。

不要随意更换 out_trade_no

重试同一笔业务时更换商户订单号,会失去幂等约束,增加重复支付的风险。

撤销和关闭不是所有产品都支持

支付宝有多种支付产品和历史接口体系。某个接口存在于统一收单产品中,不代表当前商户签约的产品一定可以调用。商户需要根据实际接入的产品确认:

  • 接口是否开放;
  • 哪些交易状态允许调用;
  • 撤销接口能否处理已支付交易;
  • 撤销失败后是否需要继续查询;
  • 接口是否有调用时限。

撤销接口返回异常后仍需查询

撤销请求也可能超时。发生这种情况时,不能直接认定撤销失败,也不能立即修改本地订单状态。商户仍需查询原交易,确认其最终状态。

退款必须使用独立幂等号

如果接口支持 out_request_no,重试同一笔退款时应保持该值不变。不同退款应使用不同的退款请求号,以免发生重复退款或造成退款记录混乱。

必须建立日终对账机制

即使已经使用实时查询和异步通知,程序故障、消息丢失或数据库异常仍可能导致交易没有得到正确处理。商户应定期使用账单或对账数据检查:

  • 支付宝显示成功,但本地订单未支付;
  • 本地订单显示支付成功,但支付宝没有对应交易;
  • 退款金额不一致;
  • 重复支付;
  • 退款长期处于处理中。

支付宝在线支付同样需要处理“单边账”,只是处理方式不同于传统 POS 冲正。它把相关工作分散到状态查询、异步通知、撤销或关闭、退款和账单对账等环节。商户系统应围绕交易状态设计补偿流程,不能直接把退款当作冲正接口使用。

备注:内容仅供参考。