微信分账三套接口族:同一笔订单第二笔被拒,重试换单号会重复出款
1. 现象:单次分账当多次用,超时重试换号
常见的两类现场。
第一类:一笔订单先调 POST https://api.mch.weixin.qq.com/secapi/pay/profitsharing(单次分账),成功后继续调多次分账或完结分账,接口拒绝。单次分账成功会把订单剩余待分账金额解冻给特约商户,订单不能再分账,也不能再完结。第二笔被拒,原因在订单已被第一笔封单,并非分账金额不足。
第二类更危险:第一次分账超时,换了 out_order_no 重试,同一接收方收到两笔。out_order_no 是商户侧分账单号,也是幂等键。V2 对 SYSTEMERROR 的处理写得很直接:不要更换商户分账单号,使用相同参数再次调用,否则可能造成资金损失。
2. 三套接口族速查
| 接口族 | 路径 | 签名/传输 | 接收方与次数 | 关键额度/限频 |
|---|---|---|---|---|
| V2 单次 | POST https://api.mch.weixin.qq.com/secapi/pay/profitsharing | 双向证书,HMAC-SHA256,sign_type=HMAC-SHA256 | receivers 不超过 50 个 JSON 对象;单次成功即封单 | 接口 30QPS;单个服务商 300QPS;单个交易收款商户 30QPS;不能设置出资子商户作为分账接受方 |
| V2 多次 | POST https://api.mch.weixin.qq.com/secapi/pay/multiprofitsharing(商户版同路径;云托管/云开发为 https://api.weixin.qq.com/_/pay/multiprofitsharing) | 双向证书,HMAC-SHA256 | 同一订单最多 50 次,每次最多 50 个接收方(早期云托管文档口径 20 次) | 单个服务商 300QPS;单个交易收款商户 30QPS;可设置出资子商户作为接收方 |
| V2 完结 | POST https://api.mch.weixin.qq.com/secapi/pay/profitsharingfinish | 双向证书,HMAC-SHA256 | 清尾,不再分给接收方 | 单个分账接收方商户 60QPS;description 必填,string 80 |
| V3 收付通 | POST /v3/ecommerce/profitsharing/orders,主域名 api.mch.weixin.qq.com,备域名 api2.mch.weixin.qq.com | JSON,Wechatpay-Serial 头;receiver_name 用微信支付公钥或平台证书公钥 RSAES-OAEP 加密 | 同一订单最多 50 次,每次最多 50 个接收方;finish 必填布尔 | 单个平台商户 2000QPS;单个交易收款商户 300QPS;分账资金冻结期默认 180 天 |
| B2b 零售小程序 | POST https://api.weixin.qq.com/retail/B2b/createprofitsharingorder?access_token=ACCESS_TOKEN&pay_sig=pay_sig,接口英文名 createProfitSharingOrder,权限集 id 158 | access_token + pay_sig | 单笔订单最多 45 个接收方;单个分账接收方只允许发起一次 | 总分账比例金额不超过订单总额 29%;部分退款场景不超过订单剩余金额 29%;冻结期默认 180 天;结算完成才能分账,沙箱不走结算 |
V2 单次主要参数:mch_id、sub_mch_id、brand_mch_id、appid、sub_appid、nonce_str、sign、sign_type、transaction_id、out_order_no、receivers、description、name。brand_mch_id 是开通连锁品牌工具后的品牌主商户号,普通分账忽略;name 是个人接收方姓名,选填,传则校验实名;out_order_no 只能是数字、大小写字母和 _-|*@。V2 完结参数:mch_id、sub_mch_id、brand_mch_id、appid、nonce_str、sign、sign_type、transaction_id、out_order_no、description。
V3 参数:appid、sub_mchid、transaction_id、out_order_no、receivers[]、finish;接收方含 PERSONAL_OPENID 时 appid 必填。V2 多次的 receivers.type 枚举 MERCHANT_ID / PERSONAL_OPENID / PERSONAL_SUB_OPENID;V3 枚举 MERCHANT_ID / PERSONAL_OPENID。B2b 参数:mchid、out_trade_no、profit_fee(单位分)、receiver_type(如 PAYEE_TYPE_EXTERNAL_MERCHANT)、receiver_account。
3. V2 三接口互斥矩阵
| 操作 | 成功后的订单状态 | 剩余待分金额 | 还能再调 |
|---|---|---|---|
| 单次分账 | 封单,不能再分账,也不能完结 | 解冻给特约商户 | 否 |
| 多次分账 | 订单仍可分 | 不会对剩余金额做任何操作;待分账金额不等于零时可再次分账 | 是,最多 50 次 |
| 完结分账 | 清尾 | 全部解冻给特约商户/本商户 | 否 |
单次分账、多次分账、完结分账应使用不同的商户分账单号;同一分账单号多次请求等同一次。单次成功即封单;多次只分指定接收方,不动剩余;完结负责清尾。已经调用单次分账后,剩余待分账金额为零,不需要再调完结。
4. V3 收付通:finish 布尔替代单次/多次两个接口
V3 只有一个分账请求入口 POST /v3/ecommerce/profitsharing/orders,用必填布尔 finish 区分 V2 的单次/多次语义。接口异步处理:先受理再处理,最终结果通过查询分账接口获取;支持幂等重入。同一笔订单最多 50 次分账请求,每次最多 50 个接收方。分账资金冻结期默认 180 天,从支付成功之日起 180 天内需发起分账,否则自动解冻给分账方。
finish | 接收方范围 | 剩余未分账金额 | 后续分账 |
|---|---|---|---|
true | 分账接收商户只支持平台商户 | 解冻回电商平台二级商户 | 解冻后不支持再次分账 |
false | 平台商户或平台二级商户 | 不会解冻回平台二级商户 | 可以再次分账 |
查询剩余待分金额用 GET /v3/profitsharing/orders/{transaction_id}/amounts。receiver_name 必须用微信支付公钥(或平台证书公钥)RSAES-OAEP 加密,Wechatpay-Serial 头必须带;不匹配实名会拒绝分账。
5. 出资方能不能当接收方
V2 单次分账明确不能设置出资子商户作为分账接受方。V2 多次分账可以设置出资子商户作为分账接受方,也可以将本商户作为分账接收方直接传入,实现释放资金给本商户的功能。这是“钱解不出来”的常见正解:单次封单后不能再分,剩余资金要用多次分账以本商户作接收方释放,或走完结分账。
V3 用 finish 控制同类语义:finish=false 可分给平台商户或平台二级商户,剩余金额不解冻,还能再分;finish=true 只支持平台商户,剩余金额解冻回二级商户,之后不能再分。B2b 的 receiver_type 如 PAYEE_TYPE_EXTERNAL_MERCHANT,出资方作为接收方的具体规则未实测。
6. 重试语义:SYSTEMERROR 与 FREQUENCY_LIMITED 都要求原单号重试
out_order_no 是商户侧分账单号,同一分账单号多次请求等同一次,换号就是新一笔。SYSTEMERROR 是接口返回错误/系统超时,处理方案是不要更换商户分账单号,使用相同参数再次调用,否则可能造成资金损失。FREQUENCY_LIMITED 表示该笔请求未受理,降低频率后原单重试,请勿更换商户分账单号。
V2 单次接口 30QPS,单个服务商 300QPS,单个交易收款商户 30QPS;完结分账单个接收方商户 60QPS。V3 单个平台商户 2000QPS,单个交易收款商户 300QPS;超限均为 FREQUENCY_LIMITED,同一主体可拆分多个商户号。V3 支持幂等重入;B2b 单个分账接收方只允许发起一次,重试语义未实测,重试前先查结果。
result_code=SUCCESS 只代表“分账申请接收成功,结果通过分账查询接口查询”,不代表终态。return_code 是通信标识,不是交易标识。V2 返回 status:PROCESSING 处理中 / FINISHED 处理完成;receivers[].result:PENDING 待分账 / SUCCESS 分账成功 / CLOSED 分账失败已关闭,CLOSED 时给 fail_reason。V2 完结的 result_code=SUCCESS 同样只表示分账完结请求接收成功,结果仍需查询。
V2 常见错误码:SYSTEMERROR、AMOUNT_OVERDUE、RECEIVER_INVALID、INVALID_TRANSACTIONID、PARAM_ERROR、INVALID_REQUEST、OPENID_MISMATCH、FREQUENCY_LIMITED、ORDER_NOT_READY、NOAUTH、NOT_SHARE_ORDER、RECEIVER_HIGH_RISK、RECEIVER_RECEIPT_LIMIT、PAYER_ACCOUNT_ABNORMAL、RECEIVER_REAL_NAME_NOT_VERIFIED、NO_RELATION、ACCOUNT_ABNORMAL。
V3 fail_reason 枚举:ACCOUNT_ABNORMAL、NO_RELATION、RECEIVER_HIGH_RISK、RECEIVER_REAL_NAME_NOT_VERIFIED、NO_AUTH、RECEIVER_RECEIPT_LIMIT、PAYER_ACCOUNT_ABNORMAL。
7. V2 与 V3 并存:迁移不是改路径
V2 走 secapi/pay/*,双向证书 + HMAC-SHA256,参数以 mch_id、sub_mch_id、sign 为主。V3 走 /v3/ecommerce/profitsharing/orders,JSON,带 Wechatpay-Serial 头,receiver_name 用 RSAES-OAEP 加密,字段是 sub_mchid、receivers[]、finish。V3 服务商分账参数 profit_sharing 为布尔值 true/false,服务商模式下分账接收方数量上限 2 万,支付方式覆盖付款码、JSAPI、Native、App、小程序、H5、委托代扣。
B2b 又是另一套:access_token + pay_sig,权限集 id 158,接收方上限 45,比例上限 29%。迁移不是改路径:接收方枚举、错误码、结果查询、剩余金额查询都不同;V2 单次/多次/完结三个 out_order_no 要分开,V3 用 finish 在一个接口里表达。
8. 可执行判据
- 查剩余待分金额:V3 用
GET /v3/profitsharing/orders/{transaction_id}/amounts;V2 以分账查询接口返回的status和receivers[].result为准,剩余金额字段未实测。B2b 以订单结算状态和已分账金额为准。 - 本地状态表至少保留:
transaction_id、out_order_no、api_family(V2/V3/B2b)、api_type(单次/多次/完结/收付通/B2b)、request_hash、result_code、status、receivers_result、fail_reason、retry_count、updated_at。唯一键用(transaction_id, out_order_no, api_type),重试只更新retry_count,不生成新号。 - 排查顺序:先确认接口族和订单状态(V2 单次是否已封单、B2b 是否结算完成);再查
out_order_no是否被换;再看result_code/status是否只是受理;然后看receivers[].result和fail_reason;最后看接收方类型、实名、关系、限额、风险,以及 QPS 是否触发FREQUENCY_LIMITED。 - 第二笔被拒:先查第一笔是否单次成功;成功即封单,不能再用多次或完结。重复出款:先查两次请求的
out_order_no是否不同;不同就是两笔,不是重试。