order_diy_offer 计算逻辑详解

本文档聚焦订单级自定义费用:o_order_diy_offer
目标是说明每类 from_name 的计算来源、计算过程、落库与汇总方式,并给出可复核的 mock 样例。


一、总览

from_name中文含义一句话总结主要来源
app_deliveryprotecDelivery Protect前端/插件先算好金额,后端仅从缓存读取并落库Redis orderDiyOfferCacheKey
app_seelSeel 保险前端/插件先算好金额,后端仅从缓存读取并落库Redis orderDiyOfferSeelCacheKey
app_randomdiscount随机优惠/随机活动前端/插件先算好金额,后端仅从缓存读取并落库Redis orderDiyOfferRandomDiscountCacheKey
customer_points积分抵扣后端按积分规则动态计算可抵扣金额并写入负值PointsBalanceService
admin_custom_price后台改价后台写入后直接读取展示,不在结账实时重算DB 现有记录

二、统一计算主链路

  1. 结账提交携带 offer_from_name(JSON 字符串)
  2. CheckoutSinglePageService::handleDiyOffer() 调用 OrderDiyOfferHandleService::saveDiyOffer()
  3. OrderDiyOfferService::saveDiyOffer() 根据 from_name 分发到各 handler
  4. handler 将费用写入 o_order_diy_offer.price(可正可负)
  5. OrderDiyOfferService::updateOrderInfo() 汇总 sum(price) 写入 order.current_offer_price
  6. CartService::getList()diy_offer_price 加到订单 total_price

计算公式(统一):

current_offer_price = sum(o_order_diy_offer.price)
 
total_price =
    subtotal_price
  + shipping_price
  + payment_price
  + tip_price
  + tax_price
  + coupon_price
  + insurance_price
  + promotion_price
  + diy_offer_price

三、各类型详细逻辑 + Mock 样例

3.1 app_deliveryprotec

一句话总结:后端不重新计算金额,只做“删旧 -> 读缓存 -> 落库 -> 汇总”。

计算流程

  1. 加锁(避免并发重复写)
  2. 删除当前订单该 from_name 历史记录
  3. 从 Redis 取 hash(每个 field 对应一段 JSON)
  4. JSON 解出多条费用项,逐条写入 o_order_diy_offer
  5. 汇总到 current_offer_price

Mock 样例

缓存内容(示例):

{
  "deliveryprotec": "[{\"title\":\"Delivery Protect\",\"price\":2.99,\"from_name\":\"app_deliveryprotec\",\"key_name\":\"delivery_protect\"}]"
}

落库后:

o_order_diy_offer.price = [2.99]
current_offer_price = 2.99

3.2 app_seel

一句话总结:与 app_deliveryprotec 同模式,金额由外部插件给定,后端仅持久化与汇总。

Mock 样例

缓存:

{
  "seel": "[{\"title\":\"Seel Worry-Free Purchase\",\"price\":1.50,\"from_name\":\"app_seel\",\"key_name\":\"seel\"}]"
}

结果:

o_order_diy_offer.price 新增 1.50
current_offer_price 在原有基础上 +1.50

3.3 app_randomdiscount

一句话总结:随机优惠金额由插件端产出,后端照单落库;若是负数则直接降低应付总价。

Mock 样例

缓存:

{
  "randomdiscount": "[{\"title\":\"Lucky Discount\",\"price\":-5.00,\"from_name\":\"app_randomdiscount\",\"key_name\":\"random_discount\"}]"
}

结果:

o_order_diy_offer.price 新增 -5.00
current_offer_price 在原有基础上 -5.00

3.4 customer_points(重点)

一句话总结:按“门槛校验 + 比例上限 + 最大积分上限”计算可抵扣金额,最终以负值写入 o_order_diy_offer.price

核心口径

  • 可抵扣基础金额不是总价全口径:
    订单金额 = 商品金额 + 优惠券 + 满减 + 运费 + 税费(均为订单币种标准价换算后)
  • 规则来自积分配置:rule.pointslimit.max_pointslimit.rule.proportion

关键公式

设:

  • customerPoints:当前用户积分余额
  • rulePoints:每 rulePoints 积分可抵 1 单位金额
  • orderPrice:订单口径金额
  • productDiscountPrice:商品口径金额
  • proportion:比例上限(如 20 表示最多抵扣 20%)

计算:

usePointsPrice = customerPoints / rulePoints
 
if max_points > 0:
    usePointsPrice = min(customerPoints, max_points) / rulePoints
 
deductionMaxPointsPrice =
    basePrice * proportion / 100
    # basePrice 取 orderPrice 或 productDiscountPrice,取决于 limit.type
 
deductionPrice = min(
    usePointsPrice,
    deductionMaxPointsPrice,
    customerPoints / rulePoints
)
 
usePoints = ceil(deductionPrice * rulePoints)
 
最终落库 price = -deductionPrice

Mock 样例(完整)

假设:

  • customerPoints = 3500
  • rulePoints = 100(100 积分 = 1)
  • max_points = 3000
  • proportion = 20%
  • limit.type = product_price
  • productDiscountPrice = 120.00

步骤:

1) 积分可兑换金额上限(按 max_points):
   usePointsPrice = min(3500, 3000) / 100 = 30.00
 
2) 比例上限金额:
   deductionMaxPointsPrice = 120.00 * 20% = 24.00
 
3) 余额理论上限:
   customerPoints / rulePoints = 3500 / 100 = 35.00
 
4) 取最小值:
   deductionPrice = min(30.00, 24.00, 35.00) = 24.00
 
5) 使用积分:
   usePoints = ceil(24.00 * 100) = 2400
 
6) 入库:
   o_order_diy_offer.price = -24.00
   values = 2400
   from_name = customer_points

结论:本次积分抵扣 24.00,扣 2400 积分。


3.5 admin_custom_price

一句话总结:后台改价是既有订单数据,结账接口读取并返回,不走实时计算。

Mock 样例

已有记录:

{
  "from_name": "admin_custom_price",
  "title": "Manual Adjustment",
  "price": -3.25
}

结果:

current_offer_price 汇总时包含 -3.25

四、组合样例(总价联动)

假设同一订单命中:

  • app_deliveryprotec = +2.99
  • app_seel = +1.50
  • app_randomdiscount = -5.00
  • customer_points = -24.00
  • admin_custom_price = -3.25

则:

current_offer_price =
    2.99 + 1.50 - 5.00 - 24.00 - 3.25
  = -27.76

若订单其它项(已算好):

  • subtotal_price = 200.00
  • shipping_price = 10.00
  • tax_price = 5.00
  • 其它项为 0

最终:

total_price = 200.00 + 10.00 + 5.00 + (-27.76) = 187.24

五、边界与注意点

  • app_* 三类(deliveryprotec/seel/randomdiscount)默认自动保存:即便前端未显式传某些 key,handler 也可能通过 autosave 机制参与。
  • 这些 app_* handler 的“金额计算”不在本服务内,服务层只负责读取缓存并持久化。
  • customer_points 在结账过程中可能触发“先退后扣”(规则变更、客户变化)以保证积分与金额一致。
  • current_offer_price 取数据库实时汇总,不是只看本次请求传入项。
  • order_diy_offer 是订单级费用,与商品级 diy_offer(promotion/bundlesale/gift/minmaxoffer)是两条并行链路。