PAYATHON 2026

支付宝是否支持同一商户下多笔订单合并支付

支付阿杰

结论

可以用一次付款结清同一商户下的多笔业务订单,但支付宝侧通常只会生成一笔交易,不会自动拆成多笔独立交易。

常见的实现方式如下:

  1. 商户系统创建一笔”合并支付单”。
  2. 将多笔业务订单关联到这笔支付单。
  3. 使用合并支付单号作为支付宝接口中的 out_trade_no,支付金额为各订单待付金额之和。
  4. 支付成功后,商户系统根据异步通知,分别将关联的业务订单更新为已支付。

支付完成后,两侧的数据表现通常是:

  • 支付宝侧只有一笔交易,对应一个 out_trade_no 和一个 trade_no。
  • 商户侧仍有多笔相互独立的业务订单,这些订单共同关联到同一笔支付宝交易。

如果支付宝侧也必须产生多笔独立交易,就需要分别发起多次支付,此时无法同时保留”一次付款”的用户体验。

为什么需要合并支付单

支付宝支付接口通常以一笔商户支付订单为交易单位。一次接口请求会提交一个商户订单号和一个支付总金额,例如:

out_trade_no = PAY202609080001
total_amount = 300.00

支付成功后,支付宝会为这笔请求生成一个支付宝交易号:

trade_no = 202609082200...

即使这笔金额对应商户内部的三个订单,支付宝也不会仅凭商品明细自动拆分出三个独立的支付交易号。

商户系统可以建立以下关系:

合并支付单 PAY202609080001
├── 业务订单 ORDER001:100.00
├── 业务订单 ORDER002:80.00
└── 业务订单 ORDER003:120.00

支付宝实付金额:300.00
支付宝交易数量:1
商户业务订单数量:3

商品明细可以用于支付页面展示或商户内部记录,但不能替代业务订单与支付单之间的关联关系。

实现步骤

1. 校验待合并的业务订单

创建支付单前,至少需要确认:

  • 所有订单属于同一个用户。
  • 所有订单属于同一个收款商户。
  • 所有订单均处于待支付状态。
  • 订单的币种和支付渠道兼容。
  • 订单未被其他支付单占用。
  • 商品金额、优惠金额、运费和应付金额已经确定。
  • 订单未过期、关闭或进入退款流程。

这里的”同一商户”应按实际的支付宝收款主体和支付应用配置判断,不能只看前端显示的店铺名称。

2. 创建合并支付单

合并支付单需要使用独立且全局唯一的编号,不应直接复用某一笔业务订单的订单号。

示例数据结构:

CREATE TABLE payment_order (
    payment_no       VARCHAR(64) PRIMARY KEY,
    user_id          VARCHAR(64) NOT NULL,
    merchant_id      VARCHAR(64) NOT NULL,
    channel          VARCHAR(32) NOT NULL,
    total_amount     DECIMAL(18, 2) NOT NULL,
    status           VARCHAR(32) NOT NULL,
    channel_trade_no VARCHAR(64),
    created_at       TIMESTAMP NOT NULL,
    paid_at          TIMESTAMP NULL
);

CREATE TABLE payment_order_item (
    payment_no  VARCHAR(64) NOT NULL,
    order_no    VARCHAR(64) NOT NULL,
    pay_amount  DECIMAL(18, 2) NOT NULL,
    PRIMARY KEY (payment_no, order_no)
);

其中:

  • payment_order 表示一次实际支付。
  • payment_order_item 保存支付单与多笔业务订单之间的关系。
  • channel_trade_no 保存支付宝返回的 trade_no。

3. 计算并固化支付金额

金额应由服务端使用精确的小数类型计算,不能使用 float 或 double。

BigDecimal totalAmount = orders.stream()
    .map(Order::getPayableAmount)
    .reduce(BigDecimal.ZERO, BigDecimal::add)
    .setScale(2, RoundingMode.UNNECESSARY);

创建支付单时,还应保存每笔子订单分摊的支付金额。支付期间不要只依赖实时重新计算,否则一旦订单价格或优惠发生变化,可能导致通知金额与订单金额不一致。

4. 向支付宝发起一笔支付

以下代码只展示合并支付涉及的核心字段。具体客户端类、接口名称和配置方式,应以实际使用的支付宝产品及对应 SDK 文档为准。

String paymentNo = "PAY202609080001";
BigDecimal totalAmount = new BigDecimal("300.00");

AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl("https://merchant.example.com/alipay/notify");
request.setReturnUrl("https://merchant.example.com/payment/result");

AlipayTradePagePayModel model = new AlipayTradePagePayModel();
model.setOutTradeNo(paymentNo);
model.setTotalAmount(totalAmount.toPlainString());
model.setSubject("多订单合并支付");
model.setProductCode("FAST_INSTANT_TRADE_PAY");

request.setBizModel(model);

AlipayTradePagePayResponse response = alipayClient.pageExecute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        "创建支付宝支付失败: " + response.getSubCode() + " " + response.getSubMsg()
    );
}

提交给支付宝的是合并支付单号 PAY202609080001,不是 ORDER001、ORDER002、ORDER003 这三个业务订单号。

手机网站支付、App 支付、小程序支付或当面付的调用方式各不相同,但基本都遵循”一个支付请求对应一笔渠道交易”的建模原则。

5. 处理支付宝异步通知

不能只根据同步跳转页面判断支付成功。商户系统需要验签异步通知,并检查:

  • 通知签名是否合法。
  • app_id、收款账号或商户身份是否符合预期。
  • out_trade_no 是否存在。
  • 通知金额是否等于合并支付单金额。
  • 交易状态是否属于可确认成功的状态。
  • 该通知是否已经处理。

确认支付成功后,应在同一个本地数据库事务中更新支付单及其子订单:

@Transactional
public void handlePaymentSuccess(
        String paymentNo,
        String alipayTradeNo,
        BigDecimal notifiedAmount) {

    PaymentOrder payment = paymentRepository.lockByPaymentNo(paymentNo)
        .orElseThrow(() -> new IllegalArgumentException("支付单不存在"));

    if (payment.isPaid()) {
        return; // 异步通知可能重复,必须支持幂等处理
    }

    if (payment.getTotalAmount().compareTo(notifiedAmount) != 0) {
        throw new IllegalStateException("支付通知金额不一致");
    }

    payment.markPaid(alipayTradeNo);
    paymentRepository.update(payment);

    List<PaymentOrderItem> items =
        paymentItemRepository.findByPaymentNo(paymentNo);

    for (PaymentOrderItem item : items) {
        orderRepository.markPaid(
            item.getOrderNo(),
            paymentNo,
            alipayTradeNo,
            item.getPayAmount()
        );
    }
}

这些业务订单可以继续保留各自的订单号、商品、履约状态和售后状态,只是共用同一个支付宝 trade_no。

退款如何处理

合并支付后,如果某笔子订单需要退款,可以按该订单在合并支付单中的实付金额,对原支付宝交易发起部分退款。

例如:

原支付宝交易金额:300.00
ORDER001 实付金额:100.00
ORDER002 实付金额:80.00
ORDER003 实付金额:120.00

取消 ORDER002 时,可以尝试对原交易退款 80.00。商户系统需要为每次退款生成唯一的退款请求号,并记录累计退款金额。

退款时需要注意:

  • 所有子订单的退款都引用同一笔原支付宝交易。
  • 累计退款金额不能超过原交易的实付金额。
  • 优惠、满减和运费的分摊必须在支付时确定,否则部分退款的金额可能无法准确计算。
  • 具体产品是否支持部分退款,以及退款期限和次数限制,应以所使用支付宝产品的当前规则为准。

对账与售后注意事项

支付交易与业务订单不能放在同一张表中

一笔支付可能对应多笔业务订单。一笔业务订单也可能经历支付失败、重新支付或更换支付渠道。因此,更合理的关系是:

业务订单 N ── 1 合并支付单 1 ── 1 支付宝交易

如果允许重新支付,关系还可能变成:

一组业务订单 ── 多次支付尝试 ── 最终一笔成功交易

发起支付后应锁定支付单内容

创建支付后,不应再向这笔支付单追加或移除子订单。如果订单发生变化,应关闭或作废原支付单,再生成新的支付单和 out_trade_no。

支付宝账单通常只显示合并交易

支付宝账单及对账文件中通常只能看到合并支付单号、支付宝交易号和总金额。商户需要通过自己的支付单关联表,将总金额还原到各笔业务订单。

防止重复履约

异步通知、主动查询和补偿任务可能同时确认同一笔支付。更新业务订单时,必须通过状态条件、数据库锁或唯一约束保证幂等,防止重复发货、重复充值或重复记账。

推荐使用”一笔合并支付单关联多笔业务订单”的方案。用户只需完成一次支付宝付款,商户系统仍可保留多笔独立订单。这些订单会共享同一笔支付宝交易,不会分别获得独立的支付宝交易号。

备注:内容仅供参考。