order_diy_offer 计算逻辑详解
本文档聚焦订单级自定义费用:
o_order_diy_offer。
目标是说明每类from_name的计算来源、计算过程、落库与汇总方式,并给出可复核的 mock 样例。
一、总览
| from_name | 中文含义 | 一句话总结 | 主要来源 |
|---|---|---|---|
app_deliveryprotec | Delivery Protect | 前端/插件先算好金额,后端仅从缓存读取并落库 | Redis orderDiyOfferCacheKey |
app_seel | Seel 保险 | 前端/插件先算好金额,后端仅从缓存读取并落库 | Redis orderDiyOfferSeelCacheKey |
app_randomdiscount | 随机优惠/随机活动 | 前端/插件先算好金额,后端仅从缓存读取并落库 | Redis orderDiyOfferRandomDiscountCacheKey |
customer_points | 积分抵扣 | 后端按积分规则动态计算可抵扣金额并写入负值 | PointsBalanceService |
admin_custom_price | 后台改价 | 后台写入后直接读取展示,不在结账实时重算 | DB 现有记录 |
二、统一计算主链路
- 结账提交携带
offer_from_name(JSON 字符串) CheckoutSinglePageService::handleDiyOffer()调用OrderDiyOfferHandleService::saveDiyOffer()OrderDiyOfferService::saveDiyOffer()根据from_name分发到各 handler- handler 将费用写入
o_order_diy_offer.price(可正可负) OrderDiyOfferService::updateOrderInfo()汇总sum(price)写入order.current_offer_priceCartService::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
一句话总结:后端不重新计算金额,只做“删旧 -> 读缓存 -> 落库 -> 汇总”。
计算流程
- 加锁(避免并发重复写)
- 删除当前订单该
from_name历史记录 - 从 Redis 取 hash(每个 field 对应一段 JSON)
- JSON 解出多条费用项,逐条写入
o_order_diy_offer - 汇总到
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.993.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.503.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.003.4 customer_points(重点)
一句话总结:按“门槛校验 + 比例上限 + 最大积分上限”计算可抵扣金额,最终以负值写入 o_order_diy_offer.price。
核心口径
- 可抵扣基础金额不是总价全口径:
订单金额 = 商品金额 + 优惠券 + 满减 + 运费 + 税费(均为订单币种标准价换算后) - 规则来自积分配置:
rule.points、limit.max_points、limit.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 = -deductionPriceMock 样例(完整)
假设:
customerPoints = 3500rulePoints = 100(100 积分 = 1)max_points = 3000proportion = 20%limit.type = product_priceproductDiscountPrice = 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.99app_seel = +1.50app_randomdiscount = -5.00customer_points = -24.00admin_custom_price = -3.25
则:
current_offer_price =
2.99 + 1.50 - 5.00 - 24.00 - 3.25
= -27.76若订单其它项(已算好):
subtotal_price = 200.00shipping_price = 10.00tax_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)是两条并行链路。