current_shipping_price 全链路(超详细版)
1. 业务定义(一句话)
依据商家物流分区配置与买家收货地址命中方案,取选中方案价格写入 current_shipping_price。
2. 后台配置到前台计算的关系
2.1 商家后台可控项(配置映射表)
| 后台配置项 | 典型存储/来源 | 前台读取点 | 对计算的直接影响 |
|---|---|---|---|
| 物流分区国家/省州范围 | shipping_zone* 表 | getShippingMethodsByCountryWithProvince | 决定方案是否可见 |
| 方案计费维度 | param.rule | getShippingCost | 按金额/件数/重量匹配阶梯 |
| 方案计费方式 | param.fee_method | getShippingCost | 固定/首重续重/首件续件 |
| 方案价格参数 | param.fee/first_weight_fee/next_weight_fee 等 | getShippingCost | 决定最终运费金额 |
| 邮编过滤规则 | param.zip_rule | getShippingCost | 方案是否对该邮编生效 |
| 运费折扣活动 | ShippingDiscountModel | handlerShippingDiscount | 在方案价格上再减 |
| 顾客标签过滤 | 顾客标签关联 | filterShippingPlansByCustomerTags | 符合条件的顾客才可见 |
| 合并策略(min/max) | store_config.shipping_zone_rule | shippingZoneRuleIsMin | 同分区多方案时取低价还是高价 |
| 自定义物流商品绑定 | 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 字段三段映射(后台页面 -> 数据库 -> 代码变量)
| 后台页面字段名(常见口径) | 数据库字段/模型 | 前台计算变量(代码) |
|---|---|---|
| 物流方案 ID | ShippingZonePlanModel.id | $shipping_id |
| 物流方案名称 | ShippingZonePlanModel.plan_name | $shipping['plan_name'] |
| 分区(国家/省州) | ShippingZoneModel 相关字段 | $countryId、$provinceId |
| 计费维度 rule | param.rule | total_price / total_quantity / total_weight |
| 计费方式 fee_method | param.fee_method | 1=固定 / 2=首重续重 / 3=首件续件 |
| 方案价格 | 物流规则计算结果(服务层返回) | $shipping_list[$shipping_id]['price'] |
| 邮编白名单 | param.zip_rule | $zipRules,决定方案是否对该邮编生效 |
| 是否可用 | 方案状态与范围命中 | in_array($shipping_id, $shipping_ids) |
| 合并策略 | store_config.shipping_zone_rule | min/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_list3.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 列表为空,无法继续结账。
- 可能根因:
- 地址不在任何分区范围;
- 方案被停用;
- 购物车触发了某些方案限制条件;
- 邮编不符合所有方案的
zip_rule。
- 核对点:
country_id/province_id;getShippingMethodsByCountryWithProvince返回结果;- 方案状态与范围配置;
zipRules过滤日志。
- 修复动作:
- 补充分区范围或启用方案;
- 修正地址参数;
- 调整限制规则后重新拉取方案。
Case 2:选中方案后价格不对
- 现象:
current_shipping_price与后台预设不一致。 - 可能根因:
- 计费规则按重量/金额触发了不同阶梯;
- 选中的是不同
shipping_id; - 换汇导致显示值差异;
- 优惠扣减导致运费计费基数变化(promotion/coupon 影响
total_price_for_shipping_cost)。
- 核对点:
- 方案
price计算时的 rule/fee_method/阶梯区间; total_price_for_shipping_cost基数是否含优惠;- 订单
currency_code与currencyExchange输入。
- 方案
- 修复动作:
- 核对计费阶梯与基数口径;
- 确认前端传参与后端选中 ID 一致;
- 统一币种口径。
Case 3:多分区合并后不知商品走哪个方案
- 现象:买家只看到一条”Shipping”,无法拆分追溯。
- 可能根因:这是系统设计如此,多分区合并无法在前台拆分展示。
- 核对点:
ShippingZoneHandlerService::getShippingZonePlanList()从缓存读取真实方案明细;o_order_shipping_zone_plan表每个分区一条记录;o_order_shipping_zone_plan_product表商品-方案绑定关系。
- 修复动作:后台从以上两张表追溯,无需改动代码。
Case 4:运费折扣未生效
- 现象:配置了运费折扣活动,但价格没有减少。
- 可能根因:
- 活动时间未到或已过期;
- 顾客标签/会员等级不满足条件;
handlerShippingDiscount在合并流程之后执行,折扣基于合并后金额。
- 核对点:
ShippingDiscountService::applyShippingDiscount入参与返回的差异。
8. 可执行检查清单(按顺序 + 预期结果)
- 检查地址参数
- 检查项:
country_id/province_id。 - 预期:与买家地址一致,且能命中分区。
- 检查项:
- 检查计费基数
- 检查项:
total_price_for_shipping_cost = Σ(final_line_price) + promotion_price + coupon_price。 - 预期:含优惠扣减后的金额。
- 检查项:
- 检查可用物流列表
- 检查项:
getShippingMethodsByCountryWithProvince(...)返回值。 - 预期:返回包含可选方案(含/不含合并)。
- 检查项:
- 检查计费公式
- 检查项:
rule维度 ×fee_method方式 × 阶梯区间 ×zip_rule。 - 预期:命中正确阶梯并算出预期金额。
- 检查项:
- 检查选中方案合法性
- 检查项:
shipping_id是否在返回列表中。 - 预期:命中;不命中应提示重新选择。
- 检查项:
- 检查合并 vs 分开
- 检查项:命中的
shipping_zone_id唯一数量。 - 预期:1个→分开展示;≥2个→合并为1条,id=-1。
- 检查项:命中的
- 检查价格换汇
- 检查项:
currencyExchange(shipping_price, current_currency)。 - 预期:
current_shipping_price与订单币种一致。
- 检查项:
- 检查落库与联动
- 检查项:
o_order.current_shipping_price/current_total_price/total_price。 - 预期:运费更新后三者联动更新。
- 检查项:
9. 执行定位速查(日志关键词 / 代码入口 / 数据落点)
9.1 日志关键词建议
getShippingMethodsByCountryWithProvincegetShippingCostsaveShippingMethodcacheShippingZonePlanListfakerShippingtotal_price_for_shipping_costshipping_id(注意与shipping_zone_id区分)current_shipping_price
9.2 代码入口(按排查顺序)
ShippingZoneService::getShippingZoneIdsByCountryWithProvince(拿分区IDs)ShippingZoneService::getShippingPlanByShippingZoneIds(拿运费方案)ShippingZoneHandlerService::getCartTotalPriceForShippingCost(组装计费基数)ShippingZoneService::getShippingCost(套公式算费)ShippingDiscountService::handlerShippingDiscount(运费折扣)ShippingZoneHandlerService::cacheShippingZonePlanList(缓存真实方案)OrderService::saveShippingMethod(写 current_shipping_price)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_id | shipping_zone_id | shipping_zone_plan_id | plan_name | shipping_price |
|---|---|---|---|---|
| 100 | A分区 | 方案A1 | ”Zone A Shipping” | 8 |
| 100 | B分区 | 方案B1 | ”Zone B Shipping” | 12 |
o_order_shipping_zone_plan_product:
| order_id | order_shipping_zone_plan_id | product_id |
|---|---|---|
| 100 | 方案A1的ID | 商品A |
| 100 | 方案B1的ID | 商品B |
o_order.current_shipping_price = 20(合并总价)