服务商+特约商户模式下订单资金如何冻结与解冻?
结论
在服务商模式下,可以通过“支付时标记为分账订单、调用分账接口、完结分账”实现资金冻结与解冻。
这里的“冻结”通常不是冻结用户账户,而是在支付成功后暂时限制订单资金,使其不能提现,也不能进入普通结算。分账完成后,再调用“完结分账”或“解冻剩余资金”接口,释放尚未分账的金额。
不使用直付通类产品也可能实现这一流程,前提是支付渠道支持服务商分账,而且服务商和特约商户都已开通相应权限。如果普通服务商支付产品无法满足分账比例、收款方类型或资金闭环等要求,就需要改用平台提供的电商资金管理产品。服务商不能自行扣留或冻结商户资金。
推荐的资金处理流程
完整流程可以这样设计:
创建支付订单
↓
将订单标记为需要分账
↓
用户支付成功
↓
支付资金进入待分账状态
↓
确认业务履约结果
↓
调用分账接口
↓
分账成功
↓
完结分账并解冻剩余资金
业务系统需要分别保存支付状态、履约状态、分账状态和解冻状态,不要用一个“订单状态”涵盖全部流程。
以微信支付服务商分账为例
如果这里的“服务商+特约商户”是指微信支付服务商模式,通常需要在下单时声明该订单用于分账。
以微信支付 API v3 的服务商支付接口为例,请求参数需要设置为:
{
"profit_sharing": true
}
请求示例如下:
{
"sp_appid": "wx_service_provider_appid",
"sp_mchid": "1900000001",
"sub_appid": "wx_sub_appid",
"sub_mchid": "1900000002",
"description": "商品订单",
"out_trade_no": "PAY202609130001",
"notify_url": "https://example.com/api/pay/notify",
"amount": {
"total": 10000,
"currency": "CNY"
},
"payer": {
"sub_openid": "USER_SUB_OPENID"
},
"profit_sharing": true
}
具体字段取决于使用的支付方式,例如 JSAPI、APP、Native 或小程序支付。接入时应以当前接口文档为准,不能把这段示例直接套用到所有支付接口。
profit_sharing: true 必须在创建支付订单时设置。普通支付订单完成支付后,通常无法再改成分账订单。
支付成功后发起分账
收到支付成功通知后,不宜立即发起分账。应先查询支付订单,确认以下信息:
- 支付状态确实为成功;
- 支付金额与本地订单一致;
- 特约商户号、应用标识与本地订单一致;
- 订单已经满足业务上的分账条件;
- 当前没有正在处理的退款或撤销流程。
确认无误后,再调用分账接口。以下是微信支付 API v3 分账请求的结构示例:
{
"appid": "wx_service_provider_appid",
"sub_mchid": "1900000002",
"transaction_id": "4200000000202609130000000001",
"out_order_no": "PROFIT202609130001",
"receivers": [
{
"type": "MERCHANT_ID",
"account": "1900000003",
"amount": 2000,
"description": "平台服务费"
}
],
"unfreeze_unsplit": false
}
金额单位通常为“分”。例如:
支付金额:10000 分
分账金额:2000 分
剩余金额:8000 分
如果之后还需要继续分账,应将 unfreeze_unsplit 设置为 false。一旦解冻剩余资金并完结分账,通常就不能再对这笔支付订单追加分账。
接收方类型、可用账户标识,以及是否需要提前添加接收方,会随支付产品和账户权限而变化。实际处理时,应以商户平台已开通的分账规则为准。
分账完成后解冻剩余资金
全部分账完成后,可以用以下两种方式释放剩余金额。
在最后一次分账时直接解冻
如果确定不再追加分账,可以在最后一次分账请求中设置:
{
"unfreeze_unsplit": true
}
支付平台处理本次分账时,会同时完结分账,并释放订单中尚未分配的资金。
单独调用解冻接口
如果此前的分账请求均设置为:
{
"unfreeze_unsplit": false
}
则应在确认所有分账单处理完成后,单独调用分账解冻接口。微信支付 API v3 的接口形式通常为:
POST /v3/profitsharing/orders/{out_order_no}/unfreeze
请求体示例:
{
"sub_mchid": "1900000002",
"transaction_id": "4200000000202609130000000001",
"description": "订单分账完成,解冻剩余资金"
}
其中,{out_order_no} 应填写对应的商户分账单号。服务商模式是否还需要传入 appid、sub_mchid 等字段,要以当前接口定义为准。
服务端调用示例
下面用 Node.js 伪代码说明业务调用关系。签名算法、证书序列号和请求头应通过支付平台官方 SDK 生成,不建议自行拼接。
async function profitShare(order) {
if (order.payStatus !== "SUCCESS") {
throw new Error("支付订单尚未成功");
}
if (order.fulfillmentStatus !== "CONFIRMED") {
throw new Error("订单尚未满足分账条件");
}
const requestBody = {
appid: order.spAppid,
sub_mchid: order.subMchid,
transaction_id: order.transactionId,
out_order_no: order.profitSharingNo,
receivers: [
{
type: "MERCHANT_ID",
account: order.receiverMchid,
amount: order.serviceFee,
description: "平台服务费"
}
],
unfreeze_unsplit: true
};
const result = await wechatPayClient.post(
"/v3/profitsharing/orders",
requestBody
);
return result;
}
如果分账和解冻分开执行,可以采用:
async function unfreezeProfitSharing(order) {
const requestBody = {
sub_mchid: order.subMchid,
transaction_id: order.transactionId,
description: "全部分账完成,解冻剩余资金"
};
return wechatPayClient.post(
`/v3/profitsharing/orders/${order.profitSharingNo}/unfreeze`,
requestBody
);
}
这两段代码只展示调用关系,不包含完整的 API v3 签名、平台证书验签和异常重试逻辑。
不使用直付通是否可行
这取决于具体的支付平台和业务资金模型。
如果普通服务商支付产品已经支持特约商户分账,一般不需要再使用直付通类产品,可以直接使用以下能力:
- 服务商为特约商户开通分账权限;
- 支付下单时声明订单需要分账;
- 支付成功后,根据履约结果发起分账;
- 全部分账完成后释放剩余资金。
遇到以下情况时,普通服务商分账可能无法满足需求:
- 需要向大量动态入驻的商户分账;
- 收款方不是支付平台允许的分账接收方;
- 所需分账比例超过账户允许的上限;
- 需要长期冻结资金;
- 需要多级分账或二次分账;
- 需要按照平台担保交易模式处理退款、结算和售后;
- 需要由平台统一管理商户余额或结算周期。
这类业务通常需要使用支付平台提供的电商收付、平台资金管理或担保交易产品。服务商不能先把全部交易资金收入自己的普通商户账户,再通过转账模拟分账。这样做无法获得支付订单级别的冻结能力,还可能带来资金合规、交易真实性和二清风险。
状态与幂等设计
支付、分账和解冻请求都可能超时,因此不能只根据 HTTP 请求是否成功来判断最终结果。建议至少保存以下字段:
out_trade_no 支付订单号
transaction_id 支付平台交易号
profit_sharing_no 本地分账单号
pay_status 支付状态
profit_sharing_status 分账状态
unfreeze_status 解冻状态
profit_sharing_amount 已分账金额
unfrozen_amount 已解冻金额
分账单号必须保持幂等。同一笔业务分账重复提交时,需要使用相同的分账单号和相同参数。请求超时后,应先查询原分账单的状态,不要立即更换单号重新提交,以免造成重复分账。
状态机可以设计为:
PAID
→ PROFIT_SHARING_PROCESSING
→ PROFIT_SHARING_SUCCESS
→ UNFREEZE_PROCESSING
→ FINISHED
如果接口返回处理中,应通过查询接口或异步通知确认最终状态。
退款处理
退款与分账直接相关,需要提前设计处理流程。
- 尚未分账:通常先处理退款,再根据最终交易结果决定是否完结分账。
- 已经分账:可能需要先执行分账回退,再申请退款。
- 已经解冻:应按照支付平台针对已完结分账订单的退款规则处理。
- 部分退款:需要核对可退金额、已分账金额,以及各接收方需要退回的金额。
分账状态不明确时,不要直接发起退款,也不能只修改本地金额。所有资金变化都应以支付平台的接口返回和查询结果为准。
注意事项
- “冻结”是支付产品对待分账资金施加的限制,不等同于冻结银行账户,也不能由业务系统自行实现。
- 下单时必须正确声明分账属性,支付成功后通常无法补设。
- 分账接收方可能需要提前建立关系,并满足实名、商户类型和业务场景等要求。
- 分账比例、分账次数、最长冻结时间和接收方数量,都可能受产品权限限制。
- 最后一次分账前不要过早解冻剩余资金。分账完结后,通常不能继续追加分账。
- 所有回调都必须验签,同时校验商户号、订单号、金额和币种。
- 请求超时后应先查询状态,不能把“未收到响应”直接视为失败。
- 接口字段和产品规则可能调整。正式接入前,应结合实际支付平台、API 版本和已开通权限,核对官方文档及商户平台配置。
备注:内容仅供参考。