☰
多商户电商的资金流怎么对齐?订单退款与提现的一次对账复盘
2026/10/2 12:11:54 网站建设 项目流程

一、一笔差了三十几块的账

去年秋天,一个县里的乡村电商平台上架了两个月,四十多个村的合作社在上面开了店,卖米、卖油、卖土鸡蛋。运营对账时发现一个商户的可提现金额对不上,系统显示能提八百二十七块六,商户自己算出来少了三十几块,来回核了三天没找出原因。这是我们接手时拿到的第一份东西,说是需求,其实只是一个现象。

把流水拉出来看才发现,这个商户中间有过一次部分退款,一笔订单退了三成,退款之后系统把整笔订单的分账金额都撤掉了,商户少拿的那部分,正好是剩下七成应该拿的钱。问题不在算错,而在于退款时动了不该动的那笔账。

二、订单状态和资金状态是两套东西

早期我们把订单当成一个字段,付款改成已付款,发货改成已发货,退款就把状态改回已退款。这套写法在只看订单的时候没问题,一旦要算钱就崩,因为订单状态的推进是单向的、整条的,而资金的流动是可逆的,还能按比例拆开。

一笔订单可能只退了其中一部分,退完还剩一部分继续履约,这种状态下订单本身仍然是已发货,可资金已经来回动了两次,一次付出一次退回。把这两件事塞进同一个字段,等于逼着一个字段同时表达两个维度,怎么改都会丢信息。

所以第一步是把它们分开。订单有一套状态机,待付款到已付款、待发货、已发货、已完成,退款作为一个能从中途插入的分支,不改变主状态的语义,只在旁边挂一条退款单。资金另有一套账,每一笔钱的进出都记一条流水,余额由流水推出来,而不是由订单状态猜出来。

三、余额到底怎么算,三条路

第一条路是每次要用余额时,把订单表里已完成的加总,减去退款,再减去已经提现的部分。这条路不用新建表,代价是所有业务都要重复写一遍这个加总,一处改口径就得全改,而且订单量上去之后,每次查余额都是一次大范围扫描。

第二条路是加一张流水表,每笔钱的进出都追一条记录,余额按商户实时求和。这条路口径统一,代价是求和本身也要扫很多行,商户中心首页一进去就要算一次余额,慢得像卡住。

第三条是我们最后用的,流水表加一层结算与冻结。流水只负责记事实,不负责算余额,另外维护三个可加总的数字,已结算、已提现、冻结中。可提现等于已结算减去已提现再减去冻结中,三个数各自有变动时增量维护,首页读的是这三个数,不再做全表加总。

四、把状态变更写成一张表

命令和事件分开,是我们做这一块时定下的第一条规矩。外部只发命令,比如发起退款、申请提现,命令进来先校验,校验过了才产生事件,状态由事件推进。这样做的直接好处是每一次状态变化都能对应一条可追溯的记录,出了问题不用猜是哪一步走歪的。

万村乐数字乡村的商家中心把这条路径写成了一张状态表,从下单到结算一共七种事件,退款和提现各占两种,剩下的三种是发货、收货和自动确认。七种事件的组合被写进了一张状态表,代码只做查表和落库,判断逻辑全部前置到校验阶段,主流程里几乎没有分支。

退款这一支单独拆出来做成退款单,一张订单可以挂多张退款单,每张退款单有自己的一次性状态,从申请到成功或者失败不可逆。支持部分退的关键就在这里,退款单上带金额,订单主表上的已退金额是成功退款单的累加,主表只做加法,从不做减法,账就不会因为一笔区间退款而整体错位。

五、金额、并发与几段关键语句

金额一律以分为单位存整数,浮点数在这条链路上一次都不出现。早期有一处用了小数计算,分账按比例乘完之后做四舍五入,几十笔累加下来差了几十厘,最后落到某一笔上就变成差一分,对账的时候怎么都对不平。改成整数之后,分账余数固定补给平台那一侧,规则简单,商户自己拿计算器也能复算。

防超卖和防负余额用的是同一种手段,带条件的更新。库存扣减的语句里带上库存足够这个条件,影响行数为零就说明被别人抢先了,直接回滚整笔下单。冻结余额的语句里带上可提现金额足够这个条件,同样靠影响行数判断结果,不靠先查一次再改一次。

分账比例要存快照。商户的分成比例是可以调的,运营每年会改一次,如果不存快照,今天算去年那批订单的分账就会套用今天的新比例,历史账全部错位。我们的做法是下单时把当时生效的比例写进订单行,结算时读订单行上的那个值,比例配置表后来改了也不影响已经产生的订单。

六、对账时暴露的三个坑

第一个坑是并发下单超卖。两个用户同时点最后一箱鸡蛋,先查库存再扣减的写法一定出问题,因为两次查询都会读到还剩一箱。改法前面提过,把判断塞进更新语句的条件里,靠数据库的行锁,只有一个能改成功,另一个影响行数为零,前端提示已售完,后台不产生半笔脏订单。

第二个坑是退款和提现撞在一起。某个商户上午申请提现,审核还没过,下午发生了一笔退款,退款要把已结算的钱减掉,如果此时按已结算来算可提现,余额就可能变成负数。改法是提现申请通过时就把金额从可提现里冻结走,退款只扣已结算,冻结中的那部分不动,真出现不够扣的情况按顺序处理,先冲减冻结再转成欠款记录挂账。

第三个坑是重复提交。网络抖动时用户会连点两次提现,两条请求都落到服务端,如果只靠前端把按钮置灰,重复单一定会产生。改法是给每笔资金操作带一个由客户端生成的操作号,服务端对操作号做去重,同一个操作号第二次进来直接返回第一次的结果,不再产生新的流水。

七、系统边界在哪里

这套账只能让系统内部的数字自洽,管不了外部资金真的到账。支付渠道的回调延迟、渠道侧的退款到账时间,都不在我们控制范围里,所以我们把渠道对账文件单独跑一遍,和本地流水做比对,差异行挂起来人工处理,而不是让余额跟着渠道状态来回跳。

它也不处理商户之间的分账纠纷。比例怎么定、退款该由谁承担、发货延迟算谁的责任,这些是合同层面的事情,系统只能在规则明确之后把结果算对,规则本身含糊的时候,算得再准也解决不了问题。

八、小结

回头看,这一块真正的转折点是把订单和资金彻底分开,订单管履约,流水管钱,余额由三个可加总的数字增量维护。分开之后最明显的收益是对账从三天缩到十几分钟,绝大多数差异都能靠流水和退款单对上,剩下的才是真正需要人工判断的疑难账。

新的村合作社开店时不用再单独讨论一遍规则,金额用分、条件更新防超卖、下单存分账快照,在万村乐数字乡村的商家中心里都成了默认继承的行为。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询