current_tax_price 全链路(超详细版)
1. 业务定义(一句话)
⚡⚡⚡ 按「可税商品 + 地址命中税率 + 优惠分摊后税基」逐行计算税额并汇总,写入 o_order.current_tax_price。
2. 后台配置到前台计算的关系
2.1 商家后台可控项(配置映射表)
| 后台配置项 | 典型存储/来源 | 前台读取点 | 对计算的直接影响 |
|---|---|---|---|
| 税规则开关/状态 | TaxModel.status | TaxService::getTaxCache | 未启用规则不会参与计算 |
| 国家税率 | TaxModel.tax_rate | getCartTaxes($countryId, ...) | 省州未命中时作为兜底税率 |
| 省州税率 | TaxAreaModel.tax_area_rate | in_array($provinceId, $area_province_ids) | 命中省州则覆盖国家税率 |
| 省州绑定范围 | TaxAreaModel.province_id | 省州列表匹配 | 决定该税率对哪些省生效 |
| 绑定商品范围 | TaxProductModel.product_id | count($v['product']) > 0 分支 | 决定哪些商品行参与某条税规则 |
| 商品可税开关 | product.taxable | getCartTaxes 逐行过滤 | taxable=0 的商品不参与任何税规则 |
| 活动优惠金额 | cart.promotion_price(负数) | getCartTaxes 分摊计算 | 减少税基,降低税额 |
| 券优惠金额 | cart.coupon_price(负数) | getCartTaxes 分摊计算 | 减少税基,降低税额 |
2.2 全链路时序图(配置 → 计算 → 落库)
flowchart TD A[商家后台配置 Tax/Area/Product] --> B["TaxService.getTaxCache(countryId)"] C[买家地址 country/province] --> D["TaxService.getCartTaxes(cart, countryId, provinceId)"] E[购物车商品行 items] --> D F[PromotionHandlerService.getCartPromotion] --> D G[CouponService.getCouponPlan] --> D D --> H{遍历每条税规则} H --> I{"count($v.product) > 0?"} I -->|是 部分商品| J[部分商品分支:过滤出命中商品] I -->|否 全场| K[全场商品分支:所有可税商品参与] J --> L{count($v.area) == 0?} K --> L L -->|是 全国| M[用国家税率 tax_rate] L -->|否| N{provinceId in area?} N -->|命中| O[用省州税率 tax_area_rate] N -->|未命中| P[回退国家税率 tax_rate] J --> Q{有 promo/coupon?} K --> Q Q -->|是| R[分摊优惠到每行:line_base = price - promo分摊 - coupon分摊] Q -->|否| S[line_base = 原价 × 数量] R --> T[税额 = max(line_base, 0) × 税率] S --> T T --> U[taxe_product_info 累计行明细] U --> V[total_tax_price = Σ行税额] V --> W[CartService/OnePage/COD 写 current_tax_price] W --> X[OrderService.renew 重算 total_price]
3. 核心入口与数据结构
3.1 核心方法链路
TaxService.php:552-794
└─ getCartTaxes($cart, $countryId, $provinceId)
│
├─ step1: 收集 taxable=1 的商品 → $taxableids
├─ step2: 调用 CouponService::getCouponPlan() + PromotionHandlerService::getCartPromotion()
├─ step3: 计算 promotion/coupon 总分摊基数
├─ step4: getTaxCache(countryId) 取所有税规则
└─ step5: 遍历规则 → 12分支计算 → 行明细 + 总税额
3.2 税规则匹配两轴
| 轴 | 值 | 含义 |
|---|---|---|
| 商品范围 | count($v['product']) == 0 | 全场:所有可税商品参与此规则 |
| 商品范围 | count($v['product']) > 0 | 部分商品:只有命中商品参与 |
| 省州范围 | count($v['area']) == 0 | 全国:该规则对所有省州生效,用国家税率 |
| 省州范围 | $provinceId in area_province_ids | 省州命中:用省州税率 |
| 省州范围 | $provinceId not in area | 未命中:回退国家税率(仍然计入该规则) |
4. 完整计算逻辑
4.1 step1-2:收集可税商品与优惠数据
// 收集 taxable=1 的商品
foreach ($cart['items'] as $product) {
if (!empty($product['product']['taxable'])) {
$taxableids[] = $product['product_id'];
}
}
// 读取 promotion/coupon(不重新计算,只取已算好的)
$couponData = (new CouponService())->getCouponPlan($cartItems, $couponCode);
$promotionData = (new PromotionHandlerService())->getCartPromotion($cart);4.2 step3:计算 promo/coupon 总分摊基数
// promotion 总适用商品金额(用于分摊比例分母)
$tadolPromotionProductPrice = Σ($product['price'] × $product['quantity']) // 仅限 promo 适用商品
// coupon 总适用商品金额(用于固定券分摊)
$tadolCouponProductPrice = Σ($product['price'] × $product['quantity']) // 仅限 coupon 适用商品4.3 step4:遍历税规则,12 分支计算
两轴交叉 × 有无优惠 = 12 分支(以「部分商品 + 省州命中」为例):
// 分支 A3:部分商品 + 省州命中 + 有优惠
if ($discount > 0 || $couponPrice > 0) {
foreach ($product as $taxProduct) {
$productPrice = $taxProduct['price'] * $taxProduct['quantity'];
// promo 分摊:该商品原价 / 所有适用商品原价 × promo总优惠
$disPrice = $productPrice / $price * abs($promotion['discount']);
// coupon 分摊:按比例分摊或按折扣率
if ($couponData['type'] == 1) {
// 折扣券:直接按折扣率算
$couponProductPrice = floor($productPrice * $couponData['param']['discount']['value']) / 100;
} else {
// 固定券:按商品金额占比分摊
$couponProductPrice = $productPrice / $tadolCouponProductPrice * $couponData['price'];
}
$netPrice = max($productPrice - $disPrice - $couponProductPrice, 0);
$lineTax = $netPrice * ($taxAreaRate / 100); // 省州税率
$totalTax += $lineTax;
}
}其他分支关键差异:
| 分支 | 税率来源 | 无优惠时公式 |
|---|---|---|
| 部分商品 + 全国 + 有优惠 | tax_rate | $val['price'] × ($v['tax_rate']/100) × $val['quantity'] |
| 部分商品 + 省州命中 + 有优惠 | tax_area_rate | 同上 |
| 部分商品 + 省州未命中 + 有优惠 | tax_rate(回退) | 同上 |
| 全场 + 全国 + 有优惠 | tax_rate | 同上 |
| 全场 + 省州命中 + 有优惠 | tax_area_rate | 同上 |
| 全场 + 省州未命中 + 有优惠 | tax_rate(回退) | 同上 |
4.4 step5:输出结构
return [
'total_tax_price' => sprintf("%.2f", $cart_taxe_price), // 汇总税额(2位小数)
'taxe_product_info' => $taxe_product, // 每行明细
];每行 taxe_product_info 子字段:
| 字段 | 含义 |
|---|---|
product_id | 商品ID |
product_price | 商品单价 |
product_quantity | 购买数量 |
tax_rate | 适用税率(国家或省州) |
tax_id | 命中税规则ID |
tax_price | 该行税额 |
coupon_price | 该行分摊的券优惠(用于对账) |
dis_price | 该行分摊的活动优惠(用于对账) |
4.5 优惠分摊核心公式
Promotion 分摊(按比例分摊):
// 适用商品总价:price × quantity 之和(promotion 适用范围内的商品)
$disPrice = ($productPrice / $tadolPromotionProductPrice) × abs($promotion['discount']);Coupon 分摊(两种模式):
// 折扣券(type=1):按折扣率直接计算,再按商品金额均摊
$couponProductPrice = ($productPrice × 折扣率) / 全场商品原价 × 券总优惠
// 固定券(type≠1):按商品金额占总分摊基数比例分摊
$couponProductPrice = ($productPrice / $tadolCouponProductPrice) × 固定面额⚡⚡⚡ 最终行税基 = max(原价 × 数量 - promo分摊 - coupon分摊, 0),不会出现负税基。
5. 字段输出结构
| 字段 | 类型 | 含义 | 示例 |
|---|---|---|---|
cart.tax_price / current_tax_price | float | 总税额(正数) | 20.00 |
cart.tax_info.total_tax_price | float | 同上,会话级汇总 | 20.00 |
cart.tax_info.taxe_product_info | array | 每行税额明细 | [{"product_id":1,"tax_price":16}] |
6. 边界场景说明
6.1 商品 taxable=0
该商品完全排除在所有税规则之外,不进入 $taxableids,不产生任何税额。
6.2 券/活动优惠金额超过商品总价
行税基 = max(original - promo - coupon, 0) → 该行税额按 0 计算,不会产生负税额。
6.3 省州未命中税规则中的省州
仍用该规则的国家税率,注释明确:“包含此商品,但是相关省份税率未找到,采用该方案的全国税率”。即全国税率是省州税率的兜底,而非跳过此规则。
6.4 多条税规则同时命中
每条税规则独立计算(商品范围无交集时各自覆盖,有交集时会重复计税?)——需注意:如果同一商品同时命中多条税规则,TaxService 的设计是每条规则各自覆盖其范围内的商品,但若范围重叠则需要确认是否有重复。建议在排障时检查 taxe_product_info 中同一 product_id 是否出现多次。
6.5 替换型券清空活动后税基变大
checkCouponUseWithPromotionStatus 中 REPLACE_WITH_PROMOTION 清空 promotion_price 后,税基不再扣减活动优惠,税额会变大。这与优惠互斥逻辑一致。
7. 与总价 total_price 的关系
total_price 的组成(9字段):
subtotal_price = Σ(final_line_price) 商品行小计
discount_price = 整单折扣(负数)
refund_price = 退款口径独立
current_total_price = subtotal + shipping 仅展示
total_price = subtotal - promotion - coupon + shipping + tax + tip + payment + insurance + offer
└─ tax_price 在此处被加(正数)
⚡⚡⚡ tax_price 是正数,在 total_price 公式中做加法。
8. 关键代码索引
| 逻辑 | 文件:行号 |
|---|---|
| 入口:TaxService.getCartTaxes | TaxService.php:552-794 |
| 收集可税商品 | TaxService.php:571-582 |
| 取优惠数据(不重算) | TaxService.php:584-590 |
| 计算 promo 分摊基数 | TaxService.php:597-614 |
| 读取税规则缓存 | TaxService.php:622-623 |
| 12 分支税计算主循环 | TaxService.php:638-780 |
| Coupon 分摊计算 | TaxService.php:798-810 |
| Promotion 分摊计算 | TaxService.php:813-870 |
| 行明细结构(taxe_product) | TaxService.php:873-887 |
| 税费汇总写入 | TaxService.php:782-793 |
| 落库触发 | OrderService::renew |
| 活动优惠计算 | PromotionHandlerService.php:17-150 |
| 券优惠计算 | CouponService.php:819-970 |