同城外卖支付结算系统搭建:商户分账、对账流程开发方案
一、结算系统业务痛点
同城外卖结算涉及用户、商家、骑手、平台多方资金拆分,同时存在退款、优惠券分摊、渠道手续费、平台佣金等复杂计算项。中小同城平台最容易出现的问题:分账规则硬编码难以调整、退款后无法冲正原有分账记录、业务订单流水和支付渠道账单对不上、资金变更无完整日志,引发商家对账纠纷。
同时需要规避二清风险,业务层只负责计算分账明细,资金划拨依托正规支付分账渠道完成,平台不直接沉淀资金。
二、整体架构与数据模型设计
结算模块和订单、支付模块解耦,采用事件驱动模式:订单完成事件触发分账计算,支付回调、退款事件触发账务冲正。
存储选型:MySQL持久化订单快照、分账明细、对账流水、结算单;Redis缓存商户结算费率、账户余额,减少频繁查询;MQ异步处理分账生成、对账任务,避免阻塞主订单链路。
核心数据表:订单结算快照表、商户分账明细表、渠道支付流水表、对账差异记录表、结算提现记录表。每一笔资金变动都新增流水记录,禁止直接更新余额字段,保证账务可追溯。
三、商户分账规则与计算逻辑
分账采用延迟结算机制,订单送达后进入冻结期,冻结期内支持售后退款,冻结期满才生成分账记录。支持按门店配置差异化佣金比例,同时区分配送费承担方、优惠券分摊规则。
一笔订单的商户应收计算公式:
商户应收 = 商品实付金额 - 平台佣金 - 渠道手续费 - 商家承担优惠金额
@Service
public class SettlementCalculateService {
/**
* 计算商户分账金额
* @param orderSnapshot 订单结算快照
* @param shopRate 商户佣金比例
* @return 分账结果
*/
public SettlementResult calcShopSettlement(OrderSettlementSnapshot orderSnapshot, BigDecimal shopRate) {
BigDecimal goodsAmount = orderSnapshot.getGoodsRealAmount();
BigDecimal commission = goodsAmount.multiply(shopRate);
BigDecimal channelFee = orderSnapshot.getChannelFee();
BigDecimal shopDiscount = orderSnapshot.getShopBearDiscount();
BigDecimal shopReceive = goodsAmount.subtract(commission).subtract(channelFee).subtract(shopDiscount);
SettlementResult result = new SettlementResult();
result.setOrderNo(orderSnapshot.getOrderNo());
result.setShopId(orderSnapshot.getShopId());
result.setCommission(commission);
result.setChannelFee(channelFee);
result.setShopReceive(shopReceive);
result.setSettleStatus(0);
return result;
}
}
分账计算完成后,生成分账明细记录,同步调用支付渠道分账接口,所有分账请求携带唯一流水号做幂等,防止重复分账。
四、退款逆向冲正处理逻辑
退款是结算链路中最容易出错的环节。发生全额/部分退款时,需要反向冲减已经生成的分账记录,同步向支付渠道发起退款分账回滚,生成红字冲正流水。
流程约束:
- 冻结期内退款:直接取消待结算分账记录,不产生资金划拨;
- 已完成分账后退款:触发逆向分账,扣减商户可结算余额,记录冲正流水;
- 退款分摊规则按照原订单优惠承担比例反向回退。
所有冲正操作单独记录,和正向分账流水一一关联,方便财务追溯。
五、对账流程开发:三方对账机制
对账分为三层核对:业务订单账、平台结算账、支付渠道账单。每日凌晨低峰期定时任务自动执行对账,按结算日期、渠道交易号进行匹配。
对账匹配逻辑:
- 拉取支付渠道当日账单文件;
- 和本地支付流水匹配,核对支付金额、退款金额;
- 支付流水匹配结算分账明细,核对佣金、商户应收;
- 匹配失败的记录写入对账差异表,标记差异类型(金额不一致、单据缺失、状态不一致),推送告警给财务运营。
@Service
public class ReconciliationService {
public void reconcileDaily(String settleDate) {
// 1. 查询本地当日结算流水
List<SettlementRecord> localList = settlementMapper.selectBySettleDate(settleDate);
// 2. 拉取渠道账单
List<ChannelBill> channelBillList = channelBillApi.getDailyBill(settleDate);
// 3. 按渠道交易号匹配比对
for(ChannelBill bill : channelBillList){
SettlementRecord record = localList.stream()
.filter(r -> r.getChannelTradeNo().equals(bill.getTradeNo()))
.findFirst().orElse(null);
if(record == null || !record.getTradeAmount().compareTo(bill.getAmount()).equals(0)){
// 写入差异记录
saveDiffRecord(bill, record, settleDate);
}
}
}
}
自动对账无法处理的差异单,进入后台人工复核队列,财务核对后标记差异处理结果。
六、商户结算单与提现能力
冻结期满后,系统按商户汇总周期内所有有效分账记录,生成周期结算单,商户端可以查看明细、下载结算对账单。商户发起提现时,校验可结算余额,调用支付渠道提现接口,提现结果异步回调更新提现状态。
余额变更全部追加流水,每一笔提现、分账、退款冲正都有对应的账务记录,不允许无日志的余额修改。
七、风控与异常兜底策略
结算异常场景:分账回调超时、渠道接口失败、对账差异持续增加。
采用重试机制,设置最大重试次数,超过次数不再自动重试,转为人工工单;同时增加资金对账告警,当日差异金额超过阈值及时通知运营。
费率配置后台增加权限管控,费率修改记录操作日志,防止费率被误改造成结算损失。
八、总结
同城外卖支付结算系统的核心思路,是把分账计算、资金划拨、对账校验做分层隔离,依靠订单快照保存交易时刻数据,正向分账、退款冲正都采用追加流水的方式保证账务不可篡改。通过可配置化的商户佣金规则,适配不同门店的运营策略,依托支付渠道的分账能力规避二清风险,再通过定时自动对账识别账实不符问题。这套方案适合中小同城外卖平台,兼顾开发成本和资金安全,随着商家规模增长,还可以独立拆分为财务结算中台,支撑多业务线统一清算。