PAYATHON 2026

服务商+特约商户模式下订单资金如何冻结与解冻?

支付阿杰

结论

在服务商模式下,可以通过“支付时标记为分账订单、调用分账接口、完结分账”实现资金冻结与解冻。

这里的“冻结”通常不是冻结用户账户,而是在支付成功后暂时限制订单资金,使其不能提现,也不能进入普通结算。分账完成后,再调用“完结分账”或“解冻剩余资金”接口,释放尚未分账的金额。

不使用直付通类产品也可能实现这一流程,前提是支付渠道支持服务商分账,而且服务商和特约商户都已开通相应权限。如果普通服务商支付产品无法满足分账比例、收款方类型或资金闭环等要求,就需要改用平台提供的电商资金管理产品。服务商不能自行扣留或冻结商户资金。

推荐的资金处理流程

完整流程可以这样设计:

创建支付订单
    ↓
将订单标记为需要分账
    ↓
用户支付成功
    ↓
支付资金进入待分账状态
    ↓
确认业务履约结果
    ↓
调用分账接口
    ↓
分账成功
    ↓
完结分账并解冻剩余资金

业务系统需要分别保存支付状态、履约状态、分账状态和解冻状态,不要用一个“订单状态”涵盖全部流程。

以微信支付服务商分账为例

如果这里的“服务商+特约商户”是指微信支付服务商模式,通常需要在下单时声明该订单用于分账。

以微信支付 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 签名、平台证书验签和异常重试逻辑。

不使用直付通是否可行

这取决于具体的支付平台和业务资金模型。

如果普通服务商支付产品已经支持特约商户分账,一般不需要再使用直付通类产品,可以直接使用以下能力:

  1. 服务商为特约商户开通分账权限;
  2. 支付下单时声明订单需要分账;
  3. 支付成功后,根据履约结果发起分账;
  4. 全部分账完成后释放剩余资金。

遇到以下情况时,普通服务商分账可能无法满足需求:

  • 需要向大量动态入驻的商户分账;
  • 收款方不是支付平台允许的分账接收方;
  • 所需分账比例超过账户允许的上限;
  • 需要长期冻结资金;
  • 需要多级分账或二次分账;
  • 需要按照平台担保交易模式处理退款、结算和售后;
  • 需要由平台统一管理商户余额或结算周期。

这类业务通常需要使用支付平台提供的电商收付、平台资金管理或担保交易产品。服务商不能先把全部交易资金收入自己的普通商户账户,再通过转账模拟分账。这样做无法获得支付订单级别的冻结能力,还可能带来资金合规、交易真实性和二清风险。

状态与幂等设计

支付、分账和解冻请求都可能超时,因此不能只根据 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

如果接口返回处理中,应通过查询接口或异步通知确认最终状态。

退款处理

退款与分账直接相关,需要提前设计处理流程。

  • 尚未分账:通常先处理退款,再根据最终交易结果决定是否完结分账。
  • 已经分账:可能需要先执行分账回退,再申请退款。
  • 已经解冻:应按照支付平台针对已完结分账订单的退款规则处理。
  • 部分退款:需要核对可退金额、已分账金额,以及各接收方需要退回的金额。

分账状态不明确时,不要直接发起退款,也不能只修改本地金额。所有资金变化都应以支付平台的接口返回和查询结果为准。

注意事项

  1. “冻结”是支付产品对待分账资金施加的限制,不等同于冻结银行账户,也不能由业务系统自行实现。
  2. 下单时必须正确声明分账属性,支付成功后通常无法补设。
  3. 分账接收方可能需要提前建立关系,并满足实名、商户类型和业务场景等要求。
  4. 分账比例、分账次数、最长冻结时间和接收方数量,都可能受产品权限限制。
  5. 最后一次分账前不要过早解冻剩余资金。分账完结后,通常不能继续追加分账。
  6. 所有回调都必须验签,同时校验商户号、订单号、金额和币种。
  7. 请求超时后应先查询状态,不能把“未收到响应”直接视为失败。
  8. 接口字段和产品规则可能调整。正式接入前,应结合实际支付平台、API 版本和已开通权限,核对官方文档及商户平台配置。

备注:内容仅供参考。