current_tax_price 全链路(超详细版)

1. 业务定义(一句话)

⚡⚡⚡ 按「可税商品 + 地址命中税率 + 优惠分摊后税基」逐行计算税额并汇总,写入 o_order.current_tax_price

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

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

后台配置项典型存储/来源前台读取点对计算的直接影响
税规则开关/状态TaxModel.statusTaxService::getTaxCache未启用规则不会参与计算
国家税率TaxModel.tax_rategetCartTaxes($countryId, ...)省州未命中时作为兜底税率
省州税率TaxAreaModel.tax_area_ratein_array($provinceId, $area_province_ids)命中省州则覆盖国家税率
省州绑定范围TaxAreaModel.province_id省州列表匹配决定该税率对哪些省生效
绑定商品范围TaxProductModel.product_idcount($v['product']) > 0 分支决定哪些商品行参与某条税规则
商品可税开关product.taxablegetCartTaxes 逐行过滤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_pricefloat总税额(正数)20.00
cart.tax_info.total_tax_pricefloat同上,会话级汇总20.00
cart.tax_info.taxe_product_infoarray每行税额明细[{"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 替换型券清空活动后税基变大

checkCouponUseWithPromotionStatusREPLACE_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.getCartTaxesTaxService.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