多商户平台(B2B2C)的业务核心是招商和运营,但真正的生命线是结算。
一笔订单从用户付款开始,资金怎么拆分、什么时候结算给商家、提现怎么审核、对账怎么对平,每一步都踩着合规红线。很多多商户平台出事,不是业务没做起来,而是结算出了问题——资金沉淀、二清嫌疑、对账对不上、提现出漏洞。
LikeShop 多商户 Java 版的结算模块,从设计之初就是围绕合规 + 自动化两条线:分账计算走订单快照、结算周期可配置、自动对账跑批、提现分级审核,在保证合规的前提下,把人工干预压到最低。
一、结算周期的基本框架
很多人对结算的理解是"订单完成就打款",这是典型的误区。多商户结算中间必须有一个售后期——防止订单刚结算完,用户又退货退款,再去向商家追钱成本极高。
一个完整的结算周期有三个关键节点:
- 订单完成时间:用户确认收货或超时自动收货;
- 售后期:订单完成后保留的售后窗口期,通常 7 天,期间发生退款会从待结算金额里扣除;
- 结算日:售后期结束后,进入正式结算周期的出账日。
LikeShop 支持按商家单独配置结算周期,不是全平台一刀切,常见三种模式:
| 周期类型 | 说明 | 适用场景 |
|---|---|---|
| T+1 日结 | 订单完成 + 售后期结束,次日结算 | 标准品类、售后率低、周转快 |
| 周结 | 每周固定日结算上周期已过售后期的订单 | 大部分品类,平衡资金压力和售后风险 |
| 月结 | 每月固定日结算上月账单 | 大商家、有账期要求、品类售后周期长 |
二、分账计算:订单级快照,按比例拆分
分账是结算的核心,也是最容易出事故的环节。
2.1 分账依据:类目佣金优先,商家默认兜底
分账比例不是全平台统一的,而是按经营类目差异化配置:
- 每个经营类目设置平台佣金比例(如服饰 3%、食品 5%、本地服务 8%);
- 商家维度有默认佣金比例,类目未配置时兜底;
- 支持重点商家单独约定特殊比例。
2.2 快照机制:按下单时比例锁定
最常见的错误做法:结算时再去查当前的佣金比例。
如果中途平台调整了类目佣金,历史订单按新比例算,要么商家少拿钱投诉,要么平台少收钱亏损。
正确的做法是下单那一刻就生成分账快照:
@Service
public class SplitAccountService {
@Autowired
private CategoryCommissionService commissionService;
/**
* 订单支付成功时,生成分账快照
*/
@Transactional(rollbackFor = Exception.class)
public void generateSplit(Order order) {
// 1. 获取下单时刻对应的佣金比例
BigDecimal commissionRate = commissionService.getRate(
order.getMerchantId(), order.getCategoryId());
// 2. 计算分账金额
BigDecimal payAmount = order.getPayAmount();
BigDecimal platformCommission = payAmount.multiply(commissionRate)
.setScale(2, RoundingMode.HALF_UP);
BigDecimal merchantAmount = payAmount.subtract(platformCommission);
// 3. 写入分账快照(永久留存,后续调佣不影响)
OrderSettle settle = new OrderSettle();
settle.setOrderId(order.getId());
settle.setPayOrderNo(order.getPayOrderNo());
settle.setMerchantId(order.getMerchantId());
settle.setCommissionRate(commissionRate);
settle.setPlatformAmount(platformCommission);
settle.setMerchantAmount(merchantAmount);
settle.setSplitStatus(SplitStatus.PENDING);
orderSettleMapper.insert(settle);
// 4. 调用支付通道分账接口(如汇付分账)
payChannelService.splitPay(order.getPayOrderNo(), buildSplitDetail(settle));
}
}
核心原则:分账比例只和下单那一刻有关,和后续任何调整无关。
2.3 资金链路:通道分账 + 系统记账
合规红线这里必须讲清楚:
分账不是平台自己算完,从自己账户转钱给商家。合规的做法是在持牌支付通道层面完成分账——用户付款时,资金按预设比例直接路由到平台佣金账户和商家子商户账户,平台不碰商家货款沉淀。
系统里的分账快照是业务台账,用来对账、结算、统计;真实资金流转在持牌支付通道内完成。这是多商户平台规避"二清"风险的核心设计。
LikeShop 多商户 Java 版的结算模块原生支持对接汇付等持牌分账通道,下单时传递分账明细,支付成功自动拆分,资金链路全程合规。
三、自动对账:每日跑批,订单级核对
人工对账是多商户运营最大的效率黑洞,也是最容易出错的环节。
3.1 对账的两端
- 通道端:支付渠道每日提供对账文件(交易明细、分账明细、退款明细);
- 系统端:平台内的订单分账快照、结算单、退款记录。
3.2 自动对账逻辑
每日凌晨定时任务跑批,全程人工零干预:
@Component
@Slf4j
public class ReconcileJob {
@Scheduled(cron = "0 30 2 * * ?")
public void dailyReconcile() {
log.info("开始每日对账跑批");
// 1. 下载通道前一日对账文件
List<ChannelOrder> channelList = payChannelService.downloadReconcileFile(
DateUtil.yesterday());
// 2. 按订单号逐笔核对
int success = 0;
int diff = 0;
for (ChannelOrder channel : channelList) {
OrderSettle settle = orderSettleMapper.getByPayOrderNo(channel.getOrderNo());
if (settle == null) {
// 系统漏单 → 告警
diff++;
reconcileLogService.addDiff(channel, "系统缺失订单");
continue;
}
if (channel.getAmount().compareTo(settle.getTotalAmount()) != 0) {
// 金额差异 → 告警
diff++;
reconcileLogService.addDiff(channel, "金额不一致");
continue;
}
success++;
}
// 3. 写入对账日志
reconcileLogService.saveRecord(
DateUtil.yesterday(), channelList.size(), success, diff);
// 4. 有差异自动告警
if (diff > 0) {
alertService.send("对账差异提醒", "当日差异订单数:" + diff);
}
log.info("对账完成,成功{},差异{}", success, diff);
}
}
3.3 差错处理
对账出现差异不是简单的"调平",要走差错处理流程:
- 系统多、通道少(短款):排查是否漏单、退款未同步通道、分账失败;
- 通道多、系统少(长款):排查是否掉单、补单入账、手续费未记账;
- 状态差异:同步订单状态,补充分账动作。
差异订单必须人工复核,不能自动调账,这是财务内控的基本要求。
四、提现审核流程
结算完成后,资金不是自动打到商家银行卡,而是进入商家可提现余额。商家发起提现申请,平台审核通过后才打款。
4.1 提现基础规则
- 最低提现额:通常 100 元,避免小额提现吃掉手续费,也降低财务工作量;
- 提现周期:工作日 T+1 审核打款,非工作日顺延;
- 手续费:按笔或按比例收取,平台可配置,可设每月免手续费次数;
- 到账时效:取决于打款通道,通常 1~3 个工作日。
4.2 三级审核机制
大额提现、高风险类目建议走三级审核,低风险可简化:
- 系统自动校验:余额是否足够、商家是否冻结、银行卡格式是否正确;
- 运营初审:核对商家身份、提现金额、历史提现记录;
- 财务复核:确认打款、回填银行流水号、标记打款完成。
@Service
public class WithdrawService {
@Transactional(rollbackFor = Exception.class)
public void auditPass(Long withdrawId, Long auditUserId) {
MerchantWithdraw withdraw = withdrawMapper.selectById(withdrawId);
if (!WithdrawStatus.APPLYING.getCode().equals(withdraw.getStatus())) {
throw new BizException("当前状态不可审核");
}
// 1. 校验可提现余额
MerchantSettle settle = merchantSettleService.getByMerchantId(
withdraw.getMerchantId());
if (settle.getAvailableBalance().compareTo(withdraw.getAmount()) < 0) {
throw new BizException("可提现余额不足");
}
// 2. 冻结对应余额,防止重复提现
merchantSettleService.freezeBalance(
withdraw.getMerchantId(), withdraw.getAmount());
// 3. 更新审核状态
withdraw.setStatus(WithdrawStatus.AUDITED.getCode());
withdraw.setAuditTime(new Date());
withdraw.setAuditUserId(auditUserId);
withdrawMapper.updateById(withdraw);
// 4. 触发打款流程
payService.transferToMerchant(withdraw);
}
}
4.3 特殊场景处理
- 商家冻结:违规商家冻结结算和提现,资金留存处理售后;
- 保证金制度:高风险类目要求缴纳保证金,售后赔付优先从保证金扣除;
- 提现驳回:驳回原因必填,冻结余额自动退回可提现余额。
五、数据库核心表设计
5.1 商家结算配置表
CREATE TABLE ls_merchant_settle (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
merchant_id BIGINT NOT NULL COMMENT '商家ID',
settle_cycle TINYINT NOT NULL COMMENT '结算周期:1日结 2周结 3月结',
sale_after_days INT NOT NULL DEFAULT 7 COMMENT '售后期(天)',
default_commission DECIMAL(5,2) NOT NULL COMMENT '默认佣金比例',
bank_card_enc VARCHAR(255) COMMENT '银行卡号(加密)',
bank_name VARCHAR(64),
real_name VARCHAR(32),
available_balance DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '可提现余额',
frozen_balance DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '冻结余额',
status TINYINT NOT NULL COMMENT '状态',
create_time DATETIME NOT NULL,
update_time DATETIME NOT NULL,
UNIQUE KEY uk_merchant_id(merchant_id)
);
5.2 订单分账快照表
CREATE TABLE ls_order_settle (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
pay_order_no VARCHAR(32) NOT NULL COMMENT '支付单号',
merchant_id BIGINT NOT NULL,
category_id BIGINT NOT NULL,
commission_rate DECIMAL(5,2) NOT NULL COMMENT '当时佣金比例',
total_amount DECIMAL(10,2) NOT NULL COMMENT '订单金额',
platform_amount DECIMAL(10,2) NOT NULL COMMENT '平台佣金',
merchant_amount DECIMAL(10,2) NOT NULL COMMENT '商家货款',
split_status TINYINT NOT NULL COMMENT '分账状态',
settle_time DATETIME COMMENT '结算时间',
create_time DATETIME NOT NULL,
INDEX idx_merchant_id(merchant_id),
INDEX idx_order_id(order_id)
);
5.3 提现申请表
CREATE TABLE ls_merchant_withdraw (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
merchant_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL COMMENT '申请金额',
fee DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '手续费',
actual_amount DECIMAL(10,2) NOT NULL COMMENT '实际打款金额',
bank_info_enc VARCHAR(255) COMMENT '打款账户信息(加密)',
status TINYINT NOT NULL COMMENT '状态',
apply_time DATETIME NOT NULL,
audit_time DATETIME,
audit_user_id BIGINT,
pay_time DATETIME,
pay_flow_no VARCHAR(64) COMMENT '打款流水号',
reject_reason VARCHAR(255),
INDEX idx_merchant_id(merchant_id),
INDEX idx_status(status)
);
六、合规红线(必须守住)
- 严禁二清:商家货款必须走持牌支付通道分账,平台不得归集商家资金后再二次结算。这是第一红线。
- 分账规则公示:佣金比例、结算周期、提现手续费,必须在商家入驻协议和店铺后台明确公示。
- 商家进件真实:营业执照、银行卡、法人信息必须实名核验,四要素一致。
- 敏感数据加密:银行卡号、法人身份证必须加密存储,日志脱敏展示。
- 提现全程留痕:申请、审核、打款、驳回每一步都要有完整记录,可审计、可追溯。
- 退款同步回分账:订单退款时,必须同步走支付通道分账回退,不能平台自己垫钱。
七、常见坑
- 佣金比例不快照:结算时查当前比例,中途调佣导致历史订单分账错误。必须按下单时刻快照。
- 退款不分账回退:只给用户退款,没从商家分账里扣回,形成平台资金窟窿。
- 对账只对总额不对明细:总额对得上,单笔对不上,出问题查不到根因。必须订单级逐笔核对。
- 提现重复打款:审核和打款是两个环节,没有幂等控制,接口重试导致重复打款。必须加幂等校验。
- 跨周期退款:订单已经结算完才发生退款,要从下一期结算金额扣,或从保证金扣除。
- 售后期计算起点错:按支付时间算售后期,应该按订单完成(确认收货)时间算。
结语
多商户平台的结算系统,从来都不是"财务模块"这么简单,它是整个平台的合规基石。
LikeShop 多商户 Java 版的这套结算体系,本质上是把分账快照、周期结算、自动对账、提现审核这几个环节做成了标准化的自动化流程,尽量减少人工干预——人工越少,出错越少,合规风险越低。
对做多商户的团队来说,结算系统属于"地基"级别的模块——地基打稳了,招商、运营、扩商家,才是顺理成章的事。