上一篇分享了《积分商城兑换架构设计:积分与现金混合支付》,反响不错,不少同学也纷纷提问:

“加上优惠券、运费,怎么算?”

“高并发下汇率怎么保障原子性读取跟持久化呀?”

“钱在微信,分在系统,怎么对账?”

混合支付的demo相对简单,但要在生产环境跑通,全是细节。

今天,极特就继续给大家分享下,复杂场景下的金额计算与对账攻坚。

复杂计费模型

在简单的“积分+现金”模型上,叠加了优惠券和运费,计算逻辑就很容易乱。先扣券还是先扣分?运费能不能用积分抵?

这里推荐一个 洋葱计算模型:由内而外,层层剥离。

图片

复杂订单金额计算流程图

原则
  • 商品总价:基数

  • 优惠优先:商家优惠券、满减活动,属于营销层,要最先扣除,因为积分一般是用来抵扣应付金额的

  • 运费独立:运费是物流成本,不参与折扣,也不支持积分抵扣,避免出现0元单,商家还要倒贴邮费

  • 积分兜底:积分抵扣的是(商品总价-优惠券)后的剩余金额

  • 现金实付:用户真正要付的钱

公式推导

现金实付 = (商品总价 - 优惠券抵扣) - 积分抵扣 + 运费

举个栗子: 商品1000元,优惠券减200元,运费10元。 用户有5000积分(抵50元)。

  • 营销层:1000 - 200 = 800元(商品应付)

  • 积分层:800 - 50 = 750元(剩余商品应付)

  • 支付层:750 + 10(运费) = 760元(最终唤起微信支付的金额)

退款分摊

买了两件商品,用了券,抵了分,还付了运费。现在用户只想退其中一件,退多少钱?

看案例:

图片

复杂退款分摊逻辑图

如上图:

上一篇分享了《积分商城兑换架构设计:积分与现金混合支付》,反响不错,不少同学也纷纷提问:

“加上优惠券、运费,怎么算?”

“高并发下汇率怎么保障原子性读取跟持久化呀?”

“钱在微信,分在系统,怎么对账?”

混合支付的demo相对简单,但要在生产环境跑通,全是细节。

今天,极特就继续给大家分享下,复杂场景下的金额计算与对账攻坚。

复杂计费模型

在简单的“积分+现金”模型上,叠加了优惠券和运费,计算逻辑就很容易乱。先扣券还是先扣分?运费能不能用积分抵?

这里推荐一个 洋葱计算模型:由内而外,层层剥离。

图片

复杂订单金额计算流程图

原则
  • 商品总价:基数

  • 优惠优先:商家优惠券、满减活动,属于营销层,要最先扣除,因为积分一般是用来抵扣应付金额的

  • 运费独立:运费是物流成本,不参与折扣,也不支持积分抵扣,避免出现0元单,商家还要倒贴邮费

  • 积分兜底:积分抵扣的是(商品总价-优惠券)后的剩余金额

  • 现金实付:用户真正要付的钱

公式推导

现金实付 = (商品总价 - 优惠券抵扣) - 积分抵扣 + 运费

举个栗子: 商品1000元,优惠券减200元,运费10元。 用户有5000积分(抵50元)。

  • 营销层:1000 - 200 = 800元(商品应付)

  • 积分层:800 - 50 = 750元(剩余商品应付)

  • 支付层:750 + 10(运费) = 760元(最终唤起微信支付的金额)

退款分摊

买了两件商品,用了券,抵了分,还付了运费。现在用户只想退其中一件,退多少钱?

看案例:

图片

复杂退款分摊逻辑图

如上图:

  • 商品A:100元

  • 商品B:200元

  • 总价:300元

  • 优惠券:-30元

  • 积分抵扣:-50元(5000分)

  • 运费:10元

  • 实付:230元

用户要退商品A,怎么算?

分摊逻辑

优惠券和积分都是针对整单的,退款时就按比例拆分:

  1. 优惠券分摊:按商品金额比例

    • 商品A占比:100 ÷ 300 = 1/3

    • 分摊金额:30 × (1/3) = 10元

    • 优惠后价格:100 - 10 = 90元

  2. 积分分摊:同样按比例

    • 分摊金额:50 × (1/3) = 16.66元

    • 对应积分:5000 × (1/3) = 1666分

  3. 运费:不退。除非整单退且未发货。

退款结果
  • 退现金:90 - 16.66 = 73.34元

  • 退积分:1666分

高并发下的原子性与持久化

下单时锁定汇率,在高并发场景下,就有问题了。直接查库?直接崩。查Redis?网络IO也是瓶颈。推荐方案:读本地内存。

简单的做法,搞个静态Map或者几个全局变量。但有个问题,多字段更新非原子性。

栗如运营同时调整了汇率和折扣上限,系统刚改了汇率,还没来得及改折扣,好巧不巧,就这时间差里,高并发来了,用户读到了新的汇率,还在用旧的折扣。

这就是典型的构建时并发问题

那怎么办?两个关键词:不可变对象(Immutable) + 原子引用(AtomicReference)。

图片

高并发汇率读取与持久化架构图

不可变对象

监听到配置变更时,在内存里new一个全新的配置对象,把所有新值填进去,过程外界完全无感知。

原子引用

对象构建完,用 AtomicReference.set()替换旧引用,CPU的CAS机制保证动作是原子的。业务线程只管 get(),拿到谁就是谁,没有中间态,要么全是旧的,要么全是新的。

快照

内存一断电就没。下单那一刻,还是要把内存里的这个快照,通过 JSON 格式持久化到订单表中。

{
  "exchange_rate": 100,     // 100积分=1元
  "points_used": 5000,      // 使用了5000分
  "deduct_amount": 50.00,     // 抵扣了50元
  "coupon_id": "CP_20231111",   // 使用了双11券
  "coupon_value": 20.00     // 优惠了20元
}

日后退款、审计,查这个快照就行了。

分布式对账闭环

钱收了,分没扣;或者分扣了,钱没收到。怎么搞?虽然有分布式事务(积分预扣+异步实扣),但网络抖动、代码Bug防不胜防。

这时候,就需要对账系统来做最后兜底。

图片

分布式混合支付对账架构图

三方对账模型

普通电商是对两方:订单 vs 微信。

混合支付是对三方:

  1. 外部渠道:微信/支付宝的账单(收了多少现金)

  2. 内部积分:积分中心的流水(扣了多少分)

  3. 业务订单:订单中心的记录(应该收多少钱、扣多少分)

流程(T+1)
  1. 获取账单:每天凌晨,自动下载微信昨日账单(CSV),拉取积分中心昨日流水

  2. 标准化:将不同格式的数据,清洗为统一的 ReconRecord(商户单号、金额、类型)

  3. 对账

  • 订单中心为基准

  • 拿订单的现金金额,去匹配微信账单

  • 拿订单的积分数额,去匹配积分流水

  1. 差错处理

  • 长款:微信有账,订单没账(通常是支付回调丢了)-> 自动补单

  • 短款:订单成功,微信没钱(极少见,通常是诈骗)-> 报警人工介入

  • 积分异常:订单成了,积分没扣-> 自动触发积分补扣指令

对账系统不介入交易,但确保每一笔交易都清清白白。

写在最后

架构设计,往往是成于宏观,死于细。

混合支付的宏观流程并不复杂,但正是优惠券的优先级、运费的特殊性、汇率的快照处理、以及对账的兜底机制,决定了一个系统的健壮性。

图片

真正的架构师,用代码画图。

我把本文同款的 AI 绘图工具包 (MDC规则 + 100款源码) 已上传至知识星球。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐