Jeepay 分账接口实战指南:绑定分账用户与发起订单分账(基于 jeepay-payment 源码解析)
【免费下载链接】jeepayJeepay是一套适合互联网企业使用的开源支付系统,支持多渠道服务商和普通商户模式。已对接微信支付,支付宝,云闪付官方接口,支持聚合码支付。项目地址: https://gitcode.com/GitHub_Trending/je/jeepay
本文以 Jeepay 支付系统分账接口文档(jeepay-payment/src/main/resources/markdown/doc/api5.md)为核心,系统讲解商户分账业务的两个核心 API——绑定分账用户(
/api/division/receiver/bind)与发起订单分账(/api/division/exec)的完整参数、请求/返回示例与调用前提,并结合jeepay-payment模块的控制器、请求体与渠道适配源码,深入剖析分账从请求校验、接收者匹配、金额计算到渠道侧调用的完整链路。读完本文,你将能够独立对接 Jeepay 分账接口,理解"按账号分账""按组自动分账""商户手动分账"三种模式的区别,并掌握分账失败时的补单与排查思路。
一、分账业务概述
分账(Profit Sharing / Royalty)是支付系统中面向多角色资金分配的核心能力。商户将交易成功的资金,按照一定的周期或比例,分账给其他方——可以是合作伙伴、员工、用户或者其他分润方。
在 Jeepay 中,分账能力由支付网关模块(jeepay-payment)统一对外提供,其接口目录位于 jeepay-payment/src/main/resources/markdown/doc/api5.md,包含两个公开接口:
| 接口 | 请求 URL | 作用 |
|---|---|---|
| 绑定分账用户 | POST /api/division/receiver/bind | 将分账接收者(账号)绑定到某个分账账号组,绑定成功后才能参与订单分账 |
| 发起订单分账 | POST /api/division/exec | 对一笔已支付成功且处于"商户手动分账"模式的订单执行分账 |
两个接口的适用对象均为:普通商户与特约商户,即既支持普通商户直连模式,也支持服务商模式下的特约商户(ISV 子商户)调用。
说明:微信、支付宝官方对分账能力均有独立的产品定义与开通要求(微信"分账"、支付宝"分账")。Jeepay 侧只是将这些渠道能力统一封装为上述两个接口,实际能否分账还取决于商户在渠道侧是否已开通对应分账权限(下文源码中
NOAUTH 无分账权限的返回即与此相关)。
二、接口通用约定
两个接口具备相同的通用规范,调用前需先了解:
- 请求方式:
POST - 请求类型:
application/json或application/x-www-form-urlencoded - 公共必传参数(由 AbstractMchAppRQ.java 定义并强制校验):
| 参数 | 说明 |
|---|---|
mchNo | 商户号(30 位以内字符串) |
appId | 商户应用 ID(24 位字符串) |
reqTime | 请求接口时间,13 位毫秒时间戳 |
version | 接口版本号,固定1.0 |
signType | 签名类型,目前仅支持MD5 |
sign | 签名值(32 位字符串),详见签名算法 |
- 返回结构(统一返回体,由 ApiRes.java 定义):
| 字段 | 类型 | 说明 |
|---|---|---|
code | int | 返回状态,0表示处理成功,其他表示处理有误(详见错误码) |
msg | String | 具体错误原因,如"签名失败""参数格式校验错误" |
sign | String | 对data内数据签名(data为空时不返回) |
data | String | 返回业务数据,JSON 格式 |
三、接口一:绑定分账用户(/api/division/receiver/bind)
分账接收者必须先绑定后分账。该接口用于把某个分账接收账号(个人或商户)绑定到指定分账账号组,绑定结果由上游渠道确认,Jeepay 侧保存绑定记录。
请求 URL:https://pay.jeepay.vip/api/division/receiver/bind(按实际部署域名替换)
3.1 请求参数
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 商户号 | mchNo | 是 | String(30) | M1621873433953 | 商户号 |
| 应用ID | appId | 是 | String(24) | 60cc09bce4b0f1c0b83761c9 | 应用ID |
| 接口代码 | ifCode | 是 | String(10) | wxpay | wxpay-微信官方接口 ; alipay-支付宝官方接口 |
| 接收者账号别名 | receiverAlias | 是 | String(64) | 张三 | 接收者账号别名 |
| 组ID | receiverGroupId | 是 | long | 10001 | 需先登录商户系统查找待加入的组ID |
| 分账接收账号类型 | accType | 是 | int | 1 | 分账接收账号类型: 0-个人(对私) 1-商户(对公) |
| 分账接收账号 | accNo | 是 | String(10) | 1231312@qq.com | 分账接收账号,微信个人是openid,支付宝可以是userId或登录名 |
| 分账接收账号名称 | accName | 否 | String(30) | 张三 | 微信选填(当填入则验证),支付宝账号必填 |
| 分账关系类型 | relationType | 是 | String(30) | PARTNER | 分账关系类型,见下方枚举说明 |
| 分账关系类型名称 | relationTypeName | 否 | String(30) | 我的员工 | 当 relationType=CUSTOM 时必填 |
| 渠道特殊信息 | channelExtInfo | 否 | String(256) | - | 渠道特殊信息 |
| 默认分账比例 | divisionProfit | 是 | String(10) | 0.3 | 若分账30%则填入 0.3 |
| 请求时间 | reqTime | 是 | long | 1622016572190 | 请求接口时间,13位时间戳 |
| 接口版本 | version | 是 | String(3) | 1.0 | 接口版本号,固定:1.0 |
| 签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 签名值,详见签名算法 |
| 签名类型 | signType | 是 | String(32) | MD5 | 签名类型,目前只支持MD5方式 |
分账关系类型(relationType)枚举(对齐微信分账关系定义):
SERVICE_PROVIDER:服务商 STORE:门店 STAFF:员工 STORE_OWNER:店主 PARTNER:合作伙伴 HEADQUARTER:总部 BRAND:品牌方 DISTRIBUTOR:分销商 USER:用户 SUPPLIER:供应商 CUSTOM:自定义当relationType=CUSTOM(自定义)时,relationTypeName必填。其余情况下,MchDivisionReceiverBindController.java 中的getRelationTypeName方法会将枚举自动映射为中文名称(如PARTNER→ "合作伙伴"),仅在映射不到时才取请求中的relationTypeName。
3.2 请求示例数据
{ 'version': '1.0', 'reqTime': '1622016572190', 'signType': 'MD5', 'sign': 'MD5MD5MD5MD5MD5MD5MD5MD5MD5MD5MD5MD5', 'mchNo': 'M1623997000', 'appId': '60cc3ba74ee0e6685f57e000', 'ifCode': 'wxpay', 'receiverAlias': '我的第一个账号', 'receiverGroupId': '100001', 'accType': '0', 'accNo': 'sfsfsd@qq.com', 'accName': '张三', 'relationType': 'OTHERS', 'relationTypeName': '我的员工', 'divisionProfit': '0.3' }3.3 返回参数
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 返回状态 | code | 是 | int | 0 | 0-处理成功,其他-处理有误,详见错误码 |
| 返回信息 | msg | 否 | String(128) | 签名失败 | 具体错误原因 |
| 签名信息 | sign | 否 | String(32) | CCD9083A6DAD9A2DA9F668C3D4517A84 | 对data内数据签名 |
| 返回数据 | data | 否 | String(512) | {} | 返回绑定数据,json格式 |
data 数据格式:
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 绑定账号ID | receiverId | 是 | long | 10001 | 绑定账号ID,订单分账将使用该ID |
| 接收者账号别名 | receiverAlias | 是 | String(64) | 张三 | 接收者账号别名 |
| 组ID | receiverGroupId | 是 | long | 10001 | 组ID |
| 分账接收账号类型 | accType | 是 | int | 1 | 分账接收账号类型: 0-个人(对私) 1-商户(对公) |
| 分账接收账号 | accNo | 是 | String(10) | 1231312@qq.com | 分账接收账号 |
| 分账接收账号名称 | accName | 否 | String(30) | 张三 | 分账接收账号名称 |
| 分账关系类型 | relationType | 是 | String(30) | PARTNER | 分账关系类型 |
| 渠道特殊信息 | channelExtInfo | 否 | String(256) | - | 渠道特殊信息 |
| 默认分账比例 | divisionProfit | 是 | String(10) | 0.3 | 默认分账比例 |
| 绑定成功时间 | bindSuccessTime | 是 | Long | 1622016572190 | 绑定成功时间(毫秒时间戳) |
| 绑定状态 | bindState | 是 | int | 1 | 绑定状态 1-绑定成功,0-绑定异常 |
| 渠道错误码 | errCode | 否 | String | ACQ.PAYMENT_AUTH_CODE_INVALID | 上游渠道返回的错误码 |
| 渠道错误描述 | errMsg | 否 | String | Business Failed 失败 | 上游渠道返回的错误描述 |
返回示例数据:
{ "code": 0, "data": { "accName": "张三", "accNo": "sfsfsd@qq.com", "accType": 0, "appId": "60cc3ba74ee0e6685f57eb1e", "bindState": 0, "divisionProfit": 0.3, "errCode": "NOAUTH", "errMsg": "无分账权限", "ifCode": "wxpay", "mchNo": "M1623997351", "receiverAlias": "我的第一个账号", "receiverGroupId": 100001, "relationType": "OTHERS", "relationTypeName": "我的员工" }, "msg": "SUCCESS", "sign": "552CB91FA1E1DB378A534B377E4E9403" }注意:上例中
code=0仅表示 Jeepay 侧请求处理成功,但data.bindState=0、errCode=NOAUTH、errMsg=无分账权限说明渠道侧绑定失败——即商户尚未在微信侧开通分账权限。这是绑定接口最典型的"业务失败"形态,调用方务必同时校验code与bindState。
3.4 源码解析:绑定流程
绑定接口由 MchDivisionReceiverBindController.java 实现,核心流程如下:
- 验签与参数解析:调用
getRQByWithMchSign(DivisionReceiverBindRQ.class)完成签名校验与参数绑定,请求体定义见 DivisionReceiverBindRQ.java。其中accType通过@Range(min=0, max=1)限制,ifCode、receiverGroupId、accNo、relationType、divisionProfit均强制非空。 - 商户应用校验:通过
configContextQueryService.queryMchInfoAndAppInfo(mchNo, appId)校验商户与应用存在性;通过payInterfaceConfigService.mchAppHasAvailableIfCode(appId, ifCode)校验该应用已配置并启用对应支付接口,否则抛出"商户应用的支付配置不存在或已关闭"。 - 分账组校验:调用
mchDivisionReceiverGroupService.findByIdAndMchNo(receiverGroupId, mchNo)校验组归属,必须属于该商户,否则提示"请进入商户平台进行创建操作"。 - 分账比例校验:
divisionProfit解析为BigDecimal后必须满足0 < 比例 ≤ 1,即取值范围为[0.0001, 1.0000]。 - 调起渠道绑定:通过
SpringBeansUtil.getBean(ifCode + "DivisionService", IDivisionService.class)按接口代码动态获取渠道实现(如wxpayDivisionService、alipayDivisionService),调用divisionService.bind(receiver, mchAppConfigContext)。 - 结果落库与返回:渠道返回
CONFIRM_SUCCESS时绑定状态置为成功(bindState=1)并落库;否则记录渠道错误码与错误信息返回调用方。
渠道适配层通过统一的 IDivisionService.java 接口抽象,其bind方法在不同渠道中映射到不同上游接口:
- 微信(WxpayDivisionService.java):V2 使用
ProfitSharingReceiverRequest(添加分账接收方),V3 使用ProfitSharingReceiverV3Request;账号类型0-个人映射为PERSONAL_OPENID,1-商户映射为MERCHANT_ID。 - 支付宝(AlipayDivisionService.java):调用
AlipayTradeRoyaltyRelationBindRequest(分账关系绑定),通过正则(RegKit.isAlipayUserId)判断accNo是userId还是loginName以决定RoyaltyEntity.type。
四、接口二:发起订单分账(/api/division/exec)
当订单下单时传入的分账模式divisionMode = 2(商户手动分账,即解冻商户金额),支付成功后支持商户手动发起订单分账。
重要前提:需要在订单支付完成后(建议 1 分钟后)再调用分账接口,避免渠道侧订单状态尚未最终确认导致分账失败。
请求 URL:https://pay.jeepay.vip/api/division/exec
4.1 请求参数
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 商户号 | mchNo | 是 | String(30) | M1621873433953 | 商户号 |
| 应用ID | appId | 是 | String(24) | 60cc09bce4b0f1c0b83761c9 | 应用ID |
| 支付订单号 | payOrderId | 否 | String(30) | P20160427210604000490 | 支付中心生成的支付订单号,与mchOrderNo二者传一即可 |
| 商户单号 | mchOrderNo | 否 | String(30) | 20160427210604000490 | 商户生成的支付单号,与payOrderId二者传一即可 |
| 是否使用系统配置的自动分账组 | useSysAutoDivisionReceivers | 是 | int | 1 | 是否使用系统配置的自动分账组: 0-否 1-是 |
| 分账接收者账号列表 | receivers | 否 | String(512) | [] | 接收者账号列表(JSONArray 转换为字符串类型),仅当 useSysAutoDivisionReceivers=0 时该字段值有效 |
| 请求时间 | reqTime | 是 | long | 1622016572190 | 请求接口时间,13位时间戳 |
| 接口版本 | version | 是 | String(3) | 1.0 | 接口版本号,固定:1.0 |
| 签名 | sign | 是 | String(32) | C380BEC2BFD727A4B6845133519F3AD6 | 签名值,详见签名算法 |
| 签名类型 | signType | 是 | String(32) | MD5 | 签名类型,目前只支持MD5方式 |
receivers 字段说明(JSONArray 序列化后的字符串):
- 方式1(按账号维度):
[{"receiverId": 800001, "divisionProfit": 0.1}]——divisionProfit不填则使用系统默认配置值。 - 方式2(按组维度):
[{"receiverGroupId": 100001, "divisionProfit": 0.1}]—— 该组所有"当前订单渠道账号且可用状态"的接收者全部参与分账;divisionProfit表示每个账号的分账比例,不填则使用系统默认配置值(建议不填写)。
4.2 请求示例数据
{ 'version': '1.0', 'reqTime': '1622016572190', 'signType': 'MD5', 'sign': '1', 'mchNo': 'M1623997351', 'appId': '60cc3ba74ee0e6685f57eb1e', 'payOrderId': 'P202108271011463510002', 'useSysAutoDivisionReceivers': '0', 'receivers': '[{"receiverGroupId":"","receiverId":"800029","divisionProfit":"0.0001"},{"receiverGroupId":"","receiverId":"800028","divisionProfit":"0.0002"}]' }4.3 返回参数
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 返回状态 | code | 是 | int | 0 | 0-处理成功,其他-处理有误,详见错误码 |
| 返回信息 | msg | 否 | String(128) | 签名失败 | 具体错误原因 |
| 签名信息 | sign | 否 | String(32) | CCD9083A6DAD9A2DA9F668C3D4517A84 | 对data内数据签名 |
| 返回数据 | data | 否 | String(512) | {} | 返回分账数据,json格式 |
data 数据格式:
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 分账状态 | state | 是 | int | 2 | 分账状态 1-分账成功,2-分账失败 |
| 上游分账批次号 | channelBatchOrderId | 否 | String(30) | T20160427210604000490 | 上游分账批次号 |
| 渠道错误码 | errCode | 否 | String | 1002 | 渠道返回错误码 |
| 渠道错误描述 | errMsg | 否 | String | ERROR | 渠道返回错误描述 |
返回示例数据:
{ "code": 0, "data": { "errCode": "unknown-sub-code", "errMsg": "Business Failed【未知的错误码ACQ.ROYALTY_ACCOUNT_NOT_EXIST】", "state": 2 }, "msg": "SUCCESS", "sign": "56836E18015DD7E4FAFE45380C0AD098" }该示例展示了一次典型的分账失败:
code=0表示请求与校验通过,但渠道侧返回ACQ.ROYALTY_ACCOUNT_NOT_EXIST(支付宝:分账接收方账号不存在),即所传receivers中的账号尚未在支付宝侧完成分账关系绑定,因此data.state=2。排查方向:先调用绑定接口确认每个 receiver 绑定成功,再发起分账。
4.4 源码解析:分账执行流程
分账执行接口由 PayOrderDivisionExecController.java 实现,核心流程如下:
- 验签与参数解析:请求体见 PayOrderDivisionExecRQ.java,其中
useSysAutoDivisionReceivers强制非空。 - 订单定位与状态校验:
payOrderId与mchOrderNo至少传一项(同时为空直接报错);通过payOrderService.queryMchOrder查询订单后,必须同时满足三个条件才允许分账:payOrder.state = STATE_SUCCESS(支付成功);payOrder.divisionState = DIVISION_STATE_UNHAPPEN(未发生过自动分账/待分账);payOrder.divisionMode = DIVISION_MODE_MANUAL(商户手动分账模式)。 否则抛出"当前订单状态不支持分账"。
- 接收者列表解析与校验:当
useSysAutoDivisionReceivers=0且receivers非空时,将 JSON 字符串反序列化为PayOrderDivisionMQ.CustomerDivisionReceiver列表;checkReceiverList逐项校验:receiverId与receiverGroupId必填一项;divisionProfit必须介于 0%~100% 之间;- 按账号维度时,校验 receiverId 集合均属于该商户、该应用、该渠道且状态可用(
state=YES); - 按组维度时,校验 receiverGroupId 集合均属于该商户。
- 执行分账:调用 PayOrderDivisionProcessService.processPayOrderDivision 完成分账处理(详见下节)。
- 结果映射:根据渠道返回状态映射
data.state——CONFIRM_SUCCESS→1(分账成功)、CONFIRM_FAIL→2(分账失败)、其余(WAITING等)→ 受理中,并回填上游分账批次号channelBatchOrderId与渠道错误信息。
4.5 分账处理核心逻辑
PayOrderDivisionProcessService.java 是分账执行的服务层核心,其关键设计:
- 状态机流转:订单
divisionState依次经历WAIT_TASK/UNHAPPEN(待分账)→ING(分账处理中)→FINISH(分账任务结束)。通过带divisionState等值条件的乐观更新防止重复发起。 - 接收者查询(queryReceiver):
useSysAutoDivisionReceivers=1时,查询商户下autoDivisionFlag=YES的自动分账组,取第一个自动分账组内的全部可用接收者;useSysAutoDivisionReceivers=0时,按mchNo + appId + ifCode + state=可用查询全部接收者,再与请求中的receivers列表按receiverId或receiverGroupId匹配过滤;若请求中某接收者携带divisionProfit,将覆盖其系统默认分账比例。
- 金额计算:以商户实际入账金额(
calMchIncomeAmount,即扣除渠道手续费的净额)作为分账基数;先按"全部分账比例总和"算出剩余待分账金额(向下取整,避免金额溢出),再逐条按分账金额 = 分账基数 × 分账比例计算,并保证"最后一个账号分账金额不超过剩余金额"。 - 记录落库:每条分账明细生成
PayOrderDivisionRecord(状态STATE_WAIT待分账),同一批次共享batchOrderId(SeqKit.genDivisionBatchId()生成),并记录订单号、渠道订单号、接收者信息、计算分账金额等快照。 - 渠道调用:通过
SpringBeansUtil.getBean(payOrder.getIfCode() + "DivisionService", IDivisionService.class)动态获取渠道实现并调用singleDivision。以支付宝为例(AlipayDivisionService.java),调用AlipayTradeOrderSettleRequest(交易结算)并设置royaltyMode="sync"(同步分账)、royaltyFinish="true"(分账完结,支付宝无完结接口因此直接完结);接收者列表为空时直接返回成功。 - 结果回写:渠道明确成功则明细更新为
STATE_SUCCESS;明确失败则更新为STATE_FAIL并记录渠道错误;WAITING(已受理)则更新为STATE_ACCEPT,等待补单任务轮询。
4.6 分账补单机制
对于渠道返回"已受理"(STATE_ACCEPT)的分账明细,PayOrderDivisionRecordReissueTask.java 提供兜底补偿:定时任务每分钟执行一次(cron = 0 0/1 * * * ?),查询"受理中且创建时间早于当前时间 5 分钟"的分账记录,按batchOrderId分组后调用渠道queryDivision查询最终结果,并将明确的成功/失败状态回写明细记录。这也是分账接口"建议支付完成后 1 分钟再调用"的原因之一——给渠道留出状态收敛时间。
五、两种分账模式的使用场景建议
| 场景 | 推荐模式 | 说明 |
|---|---|---|
| 固定合作伙伴、固定比例定期分润 | 自动分账组(下单时divisionMode=1配合系统自动分账) | 支付完成后系统按自动分账组自动触发,无需手动调用/api/division/exec |
| 每次分账对象与比例灵活变化 | 商户手动分账(下单时divisionMode=2) | 支付成功后调用/api/division/exec,通过receivers按账号或按组指定本次分账明细 |
| 同一批接收者长期复用 | 分账账号组(组 ID 模式) | 先在商户平台创建账号组,绑定接口按receiverGroupId加入,分账时按组纬度引用 |
六、对接要点与常见问题排查
- 绑定是分账的前置条件:微信、支付宝均要求分账接收者先在渠道侧完成关系绑定,否则分账时返回
ACQ.ROYALTY_ACCOUNT_NOT_EXIST(支付宝)或NOAUTH无分账权限(微信)。绑定结果以data.bindState为准,而非外层code。 - 订单状态必须匹配:发起分账前确认订单
divisionMode=2(手动分账)、支付成功且尚未分账;否则返回"当前订单状态不支持分账"。 - 分账比例校验:单个接收者比例范围为
0.0001 ~ 1.0000(绑定接口)或0% ~ 100%(分账执行接口);建议控制全部分账比例总和不超过 100%,系统会在计算时对最后一个账号按剩余金额向下取整兜底。 - 渠道权限:分账属于微信/支付宝的高权限能力,需在渠道侧单独申请开通(微信分账、支付宝分账),Jeepay 侧无法替代渠道审核。
- 幂等与补单:
receivers列表重复提交时,系统通过订单分账状态乐观锁防重;渠道"已受理"的分账会由 PayOrderDivisionRecordReissueTask.java 每分钟自动补单查询,无需业务方重复发起。 - 返回码判定:所有接口先看
code=0判断请求链路是否正常,再看data内业务状态(bindState/state)判断渠道侧结果,两者不可混淆。
七、相关参考
- 接口文档原文:jeepay-payment/src/main/resources/markdown/doc/api5.md
- 绑定接口实现:MchDivisionReceiverBindController.java
- 分账执行实现:PayOrderDivisionExecController.java
- 分账处理服务:PayOrderDivisionProcessService.java
- 渠道分账接口抽象:IDivisionService.java
- 渠道实现示例:AlipayDivisionService.java、WxpayDivisionService.java
- 分账补单任务:PayOrderDivisionRecordReissueTask.java
- 相关数据实体:MchDivisionReceiver.java、MchDivisionReceiverGroup.java、PayOrderDivisionRecord.java
【免费下载链接】jeepayJeepay是一套适合互联网企业使用的开源支付系统,支持多渠道服务商和普通商户模式。已对接微信支付,支付宝,云闪付官方接口,支持聚合码支付。项目地址: https://gitcode.com/GitHub_Trending/je/jeepay
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考