current_shipping_price 全链路(超详细版)

1. 业务定义(一句话)

依据商家物流分区配置与买家收货地址命中方案,取选中方案价格写入 current_shipping_price

2. 后台配置到前台计算的关系

2.1 商家后台可控项(配置映射表)

后台配置项典型存储/来源前台读取点对计算的直接影响
物流分区国家/省州范围shipping_zone*getShippingMethodsByCountryWithProvince决定方案是否可见
方案计费维度param.rulegetShippingCost按金额/件数/重量匹配阶梯
方案计费方式param.fee_methodgetShippingCost固定/首重续重/首件续件
方案价格参数param.fee/first_weight_fee/next_weight_feegetShippingCost决定最终运费金额
邮编过滤规则param.zip_rulegetShippingCost方案是否对该邮编生效
运费折扣活动ShippingDiscountModelhandlerShippingDiscount在方案价格上再减
顾客标签过滤顾客标签关联filterShippingPlansByCustomerTags符合条件的顾客才可见
合并策略(min/max)store_config.shipping_zone_ruleshippingZoneRuleIsMin同分区多方案时取低价还是高价
自定义物流商品绑定ShippingZoneProductModel商品级 fakerCart 单独算费商品绑定独立物流方案

2.2 全链路时序图(配置 -> 计算 -> 落库)

flowchart TD
A[商家配置物流分区/方案] --> B[ShippingZoneService 根据地址拿 shipping_zone_ids]
C[买家地址 country/province] --> B
D[购物车 items/重量/金额] --> E[getCartTotalPriceForShippingCost 组装计费基数]
E --> B
B --> F[ShippingZoneHandlerService.getShippingMethodsByCountryWithProvince]
F --> G{有自定义物流商品?}
G -->|无| H[通用物流:购物车整体算费]
G -->|有| I[自定义物流:为每个zone生成fakerCart单独算费]
I --> J[剩余商品走通用物流]
H --> K{所有商品同分区?}
J --> K
K -->|是| L[分开展示多方案,买家自选]
K -->|否| M[多分区合并:fakerShipping id=-1, 价格相加]
L --> N[handlerShippingDiscount 运费折扣]
M --> N
N --> O[cacheShippingZonePlanList 缓存真实方案明细]
O --> P[前台展示 shipping_list]
P --> Q[买家选择 shipping_id]
Q --> R[saveShippingMethod 写 current_shipping_price]
R --> S[renew 重算 current_total_price/total_price]

2.3 字段三段映射(后台页面 -> 数据库 -> 代码变量)

后台页面字段名(常见口径)数据库字段/模型前台计算变量(代码)
物流方案 IDShippingZonePlanModel.id$shipping_id
物流方案名称ShippingZonePlanModel.plan_name$shipping['plan_name']
分区(国家/省州)ShippingZoneModel 相关字段$countryId$provinceId
计费维度 ruleparam.ruletotal_price / total_quantity / total_weight
计费方式 fee_methodparam.fee_method1=固定 / 2=首重续重 / 3=首件续件
方案价格物流规则计算结果(服务层返回)$shipping_list[$shipping_id]['price']
邮编白名单param.zip_rule$zipRules,决定方案是否对该邮编生效
是否可用方案状态与范围命中in_array($shipping_id, $shipping_ids)
合并策略store_config.shipping_zone_rulemin/max,同分区多方案时选哪个

3. 前台计算主流程(参数如何参与)

3.1 关键入参与来源

  • country_id/province_id:分区命中;
  • cart.items:final_line_price/重量/件数,用于算运费基数;
  • cart.promotion_price / cart.coupon_price:参与运费计费基数(优惠后金额);
  • shipping_address.zip:邮编过滤;
  • shipping_id:最终选择的方案;
  • customer_id / visit_id:缓存 key + 顾客标签过滤。

3.2 计费基数

// ShippingZoneHandlerService::getCartTotalPriceForShippingCost()
$totalPrice  = Σ(item['final_line_price'])         // 商品行最终总价之和
$totalPrice += $cart['promotion_price']            // 加活动优惠(负数)
$totalPrice += $cart['coupon_price']               // 加券优惠(负数)
// → 含优惠的实际支付金额作为运费计费基数

⚡⚡⚡ 运费基数 = 商品总价 - 优惠金额(优惠越大运费计费基数越小)。

3.3 计费维度 × 计费方式(完整公式)

ShippingZoneService::getShippingCost() 中,按两步交叉计算:

第一步:计费维度(rule)—— 用什么值去匹配阶梯

rule含义
total_price按商品金额(优惠后)
total_quantity按商品件数
total_weight按商品重量

第二步:计费方式(fee_method)—— 怎么算钱

fee_method含义公式
1固定运费shipping_cost = fee(一口价)
2首重 + 续重first_weight_fee + ceil((weight - first_weight) / next_weight) × next_weight_fee
3首件 + 续件first_quantity_fee + ceil((qty - first_quantity) / next_quantity) × next_quantity_fee

第三步:邮编过滤(可选)

tag含义匹配方式
[r]range 范围邮编在 start-end 区间
[f]fixed 精确邮编完全等于
[s]start 前缀邮编以 val 开头
[e]end 后缀邮编以 val 结尾
[c]contain 包含邮编含有 val

邮编不匹配则该方案返回 false,不进入可选列表。

3.4 计算步骤(按代码行为)

1. 根据地址 country/province 拿 shipping_zone_ids
2. 拿这些分区下的 shipping_plans
3. 按顾客标签过滤方案可见性
4. 组装运费计费基数(含优惠的商品总价)
5. 判断是否含自定义物流商品
   ├─ 无自定义 → 购物车整体套公式 getShippingCost
   └─ 有自定义 → 每个 zone 各生成 fakerCart 单独算费,剩余商品走通用物流
6. 判断是同分区还是多分区
   ├─ 同分区 → 分开展示多个方案,买家自选
   └─ 多分区 → 合并为 fakerShipping (id=-1),价格相加,名称固定为 "Shipping"
7. 每方案过 handlerShippingDiscount 运费折扣活动
8. 缓存真实方案明细到 customerIdentity(90天)
9. 展示 shipping_list

3.5 展示与合并逻辑

分开展示(用户可自选)的条件:所有商品都在同一个 shipping_zone_id 下。

合并为一条展示的条件:存在 2 个及以上不同的 shipping_zone_id(无论自定义+通用还是自定义+自定义),此时买家只看到 1 个选项:

商品A → 自定义物流A → 方案A1 运费 = 8
商品B → 自定义物流B → 方案B1 运费 = 12
───────────────────────────────────────────
买家看到 → Shipping 合并价 = 20

合并后虚拟方案的 id = -1,真实方案明细(哪个商品对应哪个分区/方案/价格)存入 Redis 缓存。

4. 多场景演算(正常 / 边界 / 异常)

4.1 正常场景

  • 地址 US/CA;可用方案:9001=15、9002=25;
  • 选择 9001;
  • 结果:current_shipping_price=15

4.2 边界场景

  • 场景:地址切换后原 shipping_id 不再可用
    处理:抛”请重新选择物流”,不写旧价格。
  • 场景:两个自定义物流分区共存
    结果:合并成 1 条”Shipping”,各 zone 价格相加;真实方案明细存入缓存。
  • 场景:同分区有多个运费规则(如首重/续重两个阶梯)
    结果:按 rule_min / rule_max 区间命中其中一个阶梯,取该阶梯的 fee_method 计算结果。

4.3 异常场景

  • 场景:后台配置有物流,前台无方案
    排查:国家/省州是否在分区范围内、方案状态是否启用、购物车是否触发特殊限制、邮编是否被 zip_rule 过滤。
  • 场景:合并下单后想追溯每个商品走哪个方案
    排查:从缓存 getShippingZonePlanList() 读取,每个方案行含 product_ids 字段。

5. 落库与联动字段

  • 主要落库o_order.current_shipping_price(合并总价)
  • 明细落库(多分区时)o_order_shipping_zone_plan(每个分区一条)
  • 商品绑定落库(自定义物流时)o_order_shipping_zone_plan_product(商品-方案关联)
  • 直接联动current_total_price = subtotal + shipping
  • 间接联动total_price

6. 排障清单(从参数倒查)

  • 先确认地址参数 country_id/province_id
  • 再确认返回的 shipping_list 是否为空(看 $this->error);
  • 再确认运费计费基数 total_price_for_shipping_cost(含优惠);
  • 再确认 shipping_id 命中的是哪个 zone/fakerCart;
  • 最后确认是否触发 renew

7. 值班排障手册(现象 -> 根因 -> 核对点 -> 修复动作)

Case 1:前台无可选物流

  • 现象:shipping 列表为空,无法继续结账。
  • 可能根因
    1. 地址不在任何分区范围;
    2. 方案被停用;
    3. 购物车触发了某些方案限制条件;
    4. 邮编不符合所有方案的 zip_rule
  • 核对点
    • country_id/province_id
    • getShippingMethodsByCountryWithProvince 返回结果;
    • 方案状态与范围配置;
    • zipRules 过滤日志。
  • 修复动作
    • 补充分区范围或启用方案;
    • 修正地址参数;
    • 调整限制规则后重新拉取方案。

Case 2:选中方案后价格不对

  • 现象current_shipping_price 与后台预设不一致。
  • 可能根因
    1. 计费规则按重量/金额触发了不同阶梯;
    2. 选中的是不同 shipping_id
    3. 换汇导致显示值差异;
    4. 优惠扣减导致运费计费基数变化(promotion/coupon 影响 total_price_for_shipping_cost)。
  • 核对点
    • 方案 price 计算时的 rule/fee_method/阶梯区间;
    • total_price_for_shipping_cost 基数是否含优惠;
    • 订单 currency_codecurrencyExchange 输入。
  • 修复动作
    • 核对计费阶梯与基数口径;
    • 确认前端传参与后端选中 ID 一致;
    • 统一币种口径。

Case 3:多分区合并后不知商品走哪个方案

  • 现象:买家只看到一条”Shipping”,无法拆分追溯。
  • 可能根因:这是系统设计如此,多分区合并无法在前台拆分展示。
  • 核对点
    • ShippingZoneHandlerService::getShippingZonePlanList() 从缓存读取真实方案明细;
    • o_order_shipping_zone_plan 表每个分区一条记录;
    • o_order_shipping_zone_plan_product 表商品-方案绑定关系。
  • 修复动作:后台从以上两张表追溯,无需改动代码。

Case 4:运费折扣未生效

  • 现象:配置了运费折扣活动,但价格没有减少。
  • 可能根因
    1. 活动时间未到或已过期;
    2. 顾客标签/会员等级不满足条件;
    3. handlerShippingDiscount 在合并流程之后执行,折扣基于合并后金额。
  • 核对点ShippingDiscountService::applyShippingDiscount 入参与返回的差异。

8. 可执行检查清单(按顺序 + 预期结果)

  1. 检查地址参数
    • 检查项:country_id/province_id
    • 预期:与买家地址一致,且能命中分区。
  2. 检查计费基数
    • 检查项:total_price_for_shipping_cost = Σ(final_line_price) + promotion_price + coupon_price
    • 预期:含优惠扣减后的金额。
  3. 检查可用物流列表
    • 检查项:getShippingMethodsByCountryWithProvince(...) 返回值。
    • 预期:返回包含可选方案(含/不含合并)。
  4. 检查计费公式
    • 检查项:rule 维度 × fee_method 方式 × 阶梯区间 × zip_rule
    • 预期:命中正确阶梯并算出预期金额。
  5. 检查选中方案合法性
    • 检查项:shipping_id 是否在返回列表中。
    • 预期:命中;不命中应提示重新选择。
  6. 检查合并 vs 分开
    • 检查项:命中的 shipping_zone_id 唯一数量。
    • 预期:1个→分开展示;≥2个→合并为1条,id=-1。
  7. 检查价格换汇
    • 检查项:currencyExchange(shipping_price, current_currency)
    • 预期:current_shipping_price 与订单币种一致。
  8. 检查落库与联动
    • 检查项:o_order.current_shipping_price / current_total_price / total_price
    • 预期:运费更新后三者联动更新。

9. 执行定位速查(日志关键词 / 代码入口 / 数据落点)

9.1 日志关键词建议

  • getShippingMethodsByCountryWithProvince
  • getShippingCost
  • saveShippingMethod
  • cacheShippingZonePlanList
  • fakerShipping
  • total_price_for_shipping_cost
  • shipping_id(注意与 shipping_zone_id 区分)
  • current_shipping_price

9.2 代码入口(按排查顺序)

  1. ShippingZoneService::getShippingZoneIdsByCountryWithProvince(拿分区IDs)
  2. ShippingZoneService::getShippingPlanByShippingZoneIds(拿运费方案)
  3. ShippingZoneHandlerService::getCartTotalPriceForShippingCost(组装计费基数)
  4. ShippingZoneService::getShippingCost(套公式算费)
  5. ShippingDiscountService::handlerShippingDiscount(运费折扣)
  6. ShippingZoneHandlerService::cacheShippingZonePlanList(缓存真实方案)
  7. OrderService::saveShippingMethod(写 current_shipping_price)
  8. OrderService::renew(联动总价)

9.3 数据落点与核对口径

数据层表 / 缓存说明
方案配置层ShippingZoneModel / ShippingZonePlanModel商家后台配置
运费折扣层ShippingDiscountModel运费折扣活动
合并缓存层Redis(key=customerIdentity, 90天)多分区时真实方案明细
订单汇总层o_order.current_shipping_price合并后的运费总金额
订单明细层o_order_shipping_zone_plan每个分区一行,含分区名/方案名/金额
商品绑定层o_order_shipping_zone_plan_product商品ID-方案明细关联(自定义物流时)
联动层o_order.current_total_price / o_order.total_price运费更新后联动

9.4 建议核对顺序(值班直用)

先看地址 → 再看可用方案列表 → 再看计费基数(含优惠)→ 再看选中ID合法性 →再看合并 vs 分开 → 最后看落库与总价联动。

附录:合并下单落地详解(shipping_id = -1 时)

当多分区合并时,买家选的是虚拟 ID -1,下单流程如下:

1. saveShippingMethod($cart, shipping_id=-1)
       ↓
2. getShippingZonePlanList() 从缓存取真实方案列表
   (每个方案含 id/zone_id/plan_name/price/product_ids/discount_info)
       ↓
3. 循环写入 o_order_shipping_zone_plan(每个分区一条)
   - shipping_zone_id / shipping_zone_plan_id / plan_name
   - shipping_price(该分区运费)
   - shipping_discount_id / shipping_discount_plan_name(运费折扣来源)
       ↓
4. 若该分区为自定义物流(isTypeProduct)
   → 循环写入 o_order_shipping_zone_plan_product(商品-方案绑定)
   - 每个 product_id 绑定到对应方案行
       ↓
5. order_shipping_price 写入 o_order.current_shipping_price(各分区之和)
       ↓
6. renew() → 更新 total_price

示例:商品A→自定义物流A(8元),商品B→自定义物流B(12元)

o_order_shipping_zone_plan

order_idshipping_zone_idshipping_zone_plan_idplan_nameshipping_price
100A分区方案A1”Zone A Shipping”8
100B分区方案B1”Zone B Shipping”12

o_order_shipping_zone_plan_product

order_idorder_shipping_zone_plan_idproduct_id
100方案A1的ID商品A
100方案B1的ID商品B

o_order.current_shipping_price = 20(合并总价)