PC端如何实现不同二级商户的合并支付?
结论
可以实现,但通常不能直接通过直付通的 APP、H5 或小程序支付接口生成 PC 收银台二维码。
一种可行的做法是:PC 端创建合并支付订单,服务端生成对应的移动端收银页面地址,再把该地址转换为二维码。用户扫码后,通过手机进入直付通支持的 H5、小程序或 APP 支付流程,一次完成付款。支付成功后,系统根据合并订单中的明细,向多个二级商户分账或结算。
需要区分两个概念:“一次付款后分给多个二级商户”与”同一笔交易直接让多个二级商户同时收款”并不完全相同。究竟能使用合单支付、分账还是其他模式,要以商户签约产品、接口权限和支付宝开放平台当前提供的能力为准。
推荐实现方案
整体流程如下:
- PC 端向业务服务端提交多个二级商户的订单明细。
- 服务端创建唯一的合并订单,保存各二级商户及其对应金额。
- 服务端生成手机端收银页面 URL。
- PC 端将该 URL 渲染成二维码。
- 用户用手机扫码,进入 H5 页面或跳转到支付宝付款。
- 服务端通过异步通知确认支付结果。
- 支付成功后,根据原始订单明细执行分账、结算或业务侧账务拆分。
- PC 端通过轮询、WebSocket 或 Server-Sent Events 获取最终支付状态。
流程结构如下:
PC 二维码
↓
移动端收银页面
↓
直付通支持的 H5 / 小程序 / APP 支付
↓
支付成功通知
↓
按订单明细分账或结算到多个二级商户
PC 端只负责展示二维码和查询订单状态,实际支付仍发生在直付通支持的移动端场景中。
服务端创建合并订单
服务端应先创建业务系统自己的合并订单,不要直接在浏览器中拼接支付参数。一笔合并订单至少需要记录:
{
"mergeOrderNo": "M202609130001",
"totalAmount": "168.00",
"status": "WAIT_PAY",
"subOrders": [
{
"subOrderNo": "S202609130001",
"merchantId": "2088XXXXXXXX0001",
"amount": "100.00"
},
{
"subOrderNo": "S202609130002",
"merchantId": "2088XXXXXXXX0002",
"amount": "68.00"
}
]
}
创建订单时,必须校验总金额:
totalAmount = Σ subOrder.amount
计算金额时,应使用十进制定点类型,或者统一换算成”分”后用整数计算,不要直接累加 JavaScript 浮点数。
例如:
const subOrders = [
{ merchantId: "2088XXXXXXXX0001", amountFen: 10000 },
{ merchantId: "2088XXXXXXXX0002", amountFen: 6800 }
];
const totalAmountFen = subOrders.reduce(
(sum, item) => sum + item.amountFen,
0
);
console.log(totalAmountFen); // 16800
生成供扫码访问的地址
服务端创建订单后,可以返回一个短时有效的收银页面地址:
{
"mergeOrderNo": "M202609130001",
"cashierUrl": "https://pay.example.com/cashier?t=SIGNED_TOKEN",
"expireTime": "2026-09-13T15:30:00+08:00"
}
不建议在二维码中直接放入二级商户编号、金额、分账比例或支付接口参数。更稳妥的做法是只携带一次性令牌,再由服务端根据令牌查询真实订单。
示例:
import QRCode from "qrcode";
async function renderPaymentQrCode(cashierUrl) {
const canvas = document.getElementById("payment-qrcode");
await QRCode.toCanvas(canvas, cashierUrl, {
width: 240,
margin: 2,
errorCorrectionLevel: "M"
});
}
页面结构可以保持简单:
<canvas id="payment-qrcode"></canvas>
<p id="payment-status">等待扫码支付</p>
二维码过期后,应停止查询原订单并重新创建支付请求,避免用户向已经关闭或状态不明确的订单付款。
移动端发起直付通支付
用户扫码进入移动端收银页面后,再调用当前签约场景允许使用的直付通接口。
需要满足两个前提:
- 商户已经开通直付通及相应支付场景权限。
- 订单模型、二级商户字段和资金处理方式符合当前接口文档及签约规则。
使用 H5 支付时,移动端页面可以向业务服务端请求支付参数:
async function startPayment(token) {
const response = await fetch("/api/payment/create", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ token })
});
if (!response.ok) {
throw new Error("创建支付请求失败");
}
const result = await response.json();
if (result.redirectUrl) {
window.location.href = result.redirectUrl;
}
}
签名、应用私钥、平台公钥和支付接口调用都必须放在服务端。浏览器只能接收服务端生成的跳转地址或已签名参数,不能持有商户私钥。
具体调用哪个 API、各项请求字段如何填写,应以当前直付通产品文档和商户实际开通的权限为准。不能因为产品”支持 H5”,就认定某个合单或分账接口也已开放。
PC 端查询支付状态
PC 页面需要持续获取合并订单的状态。简单场景可以使用轮询:
const mergeOrderNo = "M202609130001";
const timer = setInterval(async () => {
const response = await fetch(
`/api/payment/status?mergeOrderNo=${encodeURIComponent(mergeOrderNo)}`
);
if (!response.ok) {
return;
}
const result = await response.json();
if (result.status === "SUCCESS") {
clearInterval(timer);
document.getElementById("payment-status").textContent = "支付成功";
}
if (
result.status === "CLOSED" ||
result.status === "FAILED" ||
result.status === "EXPIRED"
) {
clearInterval(timer);
document.getElementById("payment-status").textContent = "订单已关闭";
}
}, 2000);
订单量较大时,可以改用 WebSocket 或 Server-Sent Events,减轻频繁轮询给服务端带来的压力。
PC 页面展示的状态只能来自业务服务端。用户手机跳转到成功页面,并不能直接证明支付已经成功。最终结果应以平台异步通知为主,并通过主动查询进行补偿确认。
多个二级商户的资金处理方式
落地前,需要先确定业务采用哪种资金模型。
一次支付后再分账
用户先支付总金额,资金进入约定的交易账户。支付成功后,系统再按照订单明细将资金分配给多个二级商户。
这种方式在业务上最接近”合并支付”,在 PC 二维码场景中也比较容易组织。是否支持实时分账、延迟分账,以及最多支持多少个接收方,需要根据签约产品确认。
主单加子单
业务系统创建一笔主订单,并为每个二级商户分别创建子订单。用户只需付款一次,系统内部则保留各商户独立的订单和金额。
直付通是否允许在一次支付请求中直接提交多个子订单,取决于当前接口能力。不能自行假设普通 H5 支付接口具备合单能力。
业务侧统一收款后结算
如果现有支付能力不能直接覆盖多个二级商户,可以由一个主体统一收款,再通过平台允许的结算能力完成后续资金划拨。
这种方式涉及交易主体、开票、退款、对账、税务和资金合规,不能只靠技术上的订单拆分来处理。上线前应由支付服务方和业务合规人员确认。
功能相近的替代方案
如果直付通目前没有适用的合单接口,可以考虑以下方案:
- 使用支持 PC 扫码的当面付或预创建类产品统一收款,再结合平台允许的分账能力处理多个二级商户。
- 把直付通 H5 收银页面作为二维码落地页,PC 端只负责展示二维码。
- 将一笔合并订单拆成多笔支付。实现相对简单,但用户需要付款多次,严格来说不属于一次合并支付。
- 接入具备”合单支付加多方分账”能力的其他支付产品。切换前,需要确认该产品允许的商户关系、资金路径和结算规则。
当面付或预创建类产品能否与直付通二级商户、分账或结算能力结合使用,必须向支付宝侧确认。不同产品的接口可以在业务层串联,但这不表示它们的资金能力和签约权限可以直接混用。
退款与对账设计
设计合并支付时,除了付款流程,还要提前考虑部分退款和全额退款。
例如,总订单金额为 168 元,两个子订单分别为 100 元和 68 元。如果第二个子订单需要退款,系统必须明确:
- 是否允许单独退还 68 元;
- 已经分账的资金是否需要先解冻或退回;
- 平台退款接口使用主订单号还是子订单号;
- 手续费和优惠如何分摊;
- 退款后如何调整各二级商户的可结算金额。
业务库应同时保存支付订单、子订单、分账记录、退款记录和异步通知原文,并为每项操作设置独立的幂等号。
注意事项
- 不要在前端保存应用私钥、支付签名密钥或完整的支付参数。
- 二维码应设置有效期,并使用不可预测、可校验且最好只能使用一次的令牌。
- 服务端必须验证异步通知的签名、应用标识、订单号、金额和收款主体。
- 支付创建、异步通知、主动查询、分账和退款接口都必须做幂等处理。
- 合并订单总金额必须等于所有子订单金额之和。
- 在确认支付成功前,不要发货或执行分账。
- 异步通知处理失败后应支持重试。长时间未收到通知时,应主动查询订单。
- 需要确认二级商户数量、单笔金额、分账比例、分账时效和退款限制。
- PC 二维码只是支付入口,不会扩大直付通已经签约的产品权限。
- “统一收款后自行转账给商户”可能改变资金性质。在没有确认合规性之前,不应将其作为平台分账的替代方案。
较为稳妥的落地方式是:PC 展示二维码,手机端使用受支持的支付场景,服务端维护合并订单,并在支付成功后按规则分账。如果业务要求支付接口直接生成多个二级商户的收款指令,还需要确认直付通当前是否已向该商户开放对应的合单能力。在没有明确接口文档或签约权限前,不宜基于普通 H5 支付接口自行扩展。
备注:内容仅供参考。