current_payment_price 全链路(超详细版)
1. 业务定义(一句话)
⚡⚡⚡ 买家选择支付方式后,系统按「固定费 + 比例费」计算手续费,以 total_price - payment_price 为基数,并通过 lockMaxOrderPrice 封顶修正后写入 current_payment_price。
2. 后台配置到前台计算的关系
2.1 商家后台可控项(配置映射表)
| 后台配置项 | 典型存储/来源 | 前台读取点 | 对计算的直接影响 |
|---|---|---|---|
| 手续费开关 | o_payment.formula | getPaymentFee | 0=关闭→手续费为0 |
| 固定费 | o_payment.formula_param.price | getPaymentFee | 固定附加金额 |
| 比例费 | o_payment.formula_param.percentage | getPaymentFee | 按基数百分比收取 |
| 展示条件 | o_payment.display_param | getPaymentFee | 10+种条件过滤,决定是否展示 |
| 支付方式ID | o_payment.id | 买家选择 | 匹配具体费率和规则 |
| 会员封顶策略 | cart.minmaxoffer_max_order_price | lockMaxOrderPrice | 影响最终手续费 |
2.2 全链路时序图(配置 → 计算 → 落库)
flowchart TD A[商家配置支付方式] --> B[PaymentService.getPaymentMethodsWithCart] B --> C{遍历每种支付方式} C --> D[display_param 条件过滤] D -->|不通过| E[该支付方式返回 false,不展示] D -->|通过| F[getPaymentFee 计算手续费] F --> G[基数 = total_price - payment_price] G --> H[fee = fixed + round(基数 × percentage ÷ 100, 2)] H --> I[lockMaxOrderPrice 封顶修正] I --> J[返回支付列表含手续费] J --> K[买家选择 payment_id] K --> L[savePaymentMethod 写 current_payment_price] L --> M[renew 重算 total_price]
3. 核心入口与数据结构
3.1 核心方法链路
PaymentService.php:442-513
└─ getPaymentFee($cart, $payment_param, $payment_formula, $display_param)
├─ 步骤1: 基数 = total_price - payment_price(避免循环依赖)
├─ 步骤2: display_param 10+种条件过滤
├─ 步骤3: fee = fixed + round(基数 × percentage / 100, 2)
└─ 步骤4: lockMaxOrderPrice 封顶修正
3.2 display_param 展示条件完整清单
| 条件字段 | 逻辑 | 不通过时 |
|---|---|---|
morethan_none | total_price > morethan_none → 隐藏 | 金额过高时不展示 |
lessthan_none | total_price <= lessthan_none → 隐藏 | 金额过低时不展示 |
country_whitelist | country_code 不在列表 → 隐藏 | 特定国家外不可用 |
country_blacklist | country_code 在列表 → 隐藏 | 特定国家内不可用 |
is_bill_address | 账单地址缺失 → 隐藏 | 需提供账单地址 |
product_type_whitelist | 任何商品 type 不在列表 → 隐藏 | 白名单商品类型外不可用 |
product_type_blacklist | 任何商品 type 在列表 → 隐藏 | 黑名单商品类型外不可用 |
customerDisplay() | 顾客订单数/消费额/标签不符 → 隐藏 | 新客/老客/特定标签限制 |
domain_list | 当前域名不在列表 → 隐藏 | 特定域名限定 |
shipping_zone_plan_whitelist | 物流方案名称不在列表 → 隐藏 | 特定物流方式限定 |
4. 完整计算逻辑
4.1 基数计算(防止循环依赖)
$total_price = $cart['total_price'] - $cart['payment_price'];
// 即:当前应付金额 - 已有的手续费
// 这样 first iteration 时 payment_price=0,基数就是总价本身
// 后续迭代时,基数 = 总价 - 已计算的手续费,避免手续费的"手续费"滚大4.2 手续费公式
if (empty($payment_formula)) {
return $this->lockMaxOrderPrice($cart, $total_price, 0);
}
$payment_fee = (floatval($payment_param->price) ?? 0)
+ round(($total_price * floatval($payment_param->percentage) ?? 0) / 100, 2);
return $this->lockMaxOrderPrice($cart, $total_price, $payment_fee);公式:
fee = fixed_price + round((基数 × percentage) / 100, 2)
⚡ 比例分母是 100(不是 1000),即 percentage=3 表示 3%。
4.3 lockMaxOrderPrice 封顶修正
// 读取会员封顶策略
$max = $cart['minmaxoffer_max_order_price'] ?? 0;
if (!empty($cart['minmaxoffer_diff_price']) && $max <= 0) {
// 差值模式:修正订单小计
$order->current_subtotal_price += currencyExchange($cart['minmaxoffer_diff_price'], $cart['currency']);
}
if ($max > 0 && 会员封顶条件满足) {
// 封顶模式:若 total_price + payment_fee > max,则减少 payment_fee 补差
if (($total_price + $paymentFee) > $max) {
$paymentFee += ($max - ($total_price + $paymentFee));
}
}
return round($paymentFee, 2);⚡ 封顶修正语义:保证 total_price + payment_fee ≤ max_order_price,通过减少手续费实现(而非减少商品金额)。
4.4 输出
// getPaymentFee 返回 float(手续费金额)或 false(不展示)
return $payment_fee; // 或 false
// 在 getPaymentMethodsWithCart 中写入返回数组:
'price' => $payment_fee,
'original_price' => $payment_fee,5. 字段输出结构
| 字段 | 类型 | 含义 | 示例 |
|---|---|---|---|
cart.payment.price | float | 该支付方式手续费(正数) | 2.50 |
cart.payment.original_price | float | 同上 | 2.50 |
cart.payment_methods[] | array | 所有可用支付方式列表 | 含各通道名称/ID/手续费 |
o_order.current_payment_price | float | 已选支付方式的手续费 | 2.50 |
6. 边界场景说明
6.1 首次加载时 payment_price 为 0
基数 = total_price - payment_price = total_price - 0 = total_price,全额作为手续费计算基数。
6.2 比例 percentage = 0
只收固定费,无比例附加。
6.3 fixed_price = 0
只收比例费(无固定附加)。
6.4 display_param 条件全部不满足
getPaymentFee 返回 false,该支付方式不进入可用列表。
6.5 lockMaxOrderPrice 截断后 payment_fee 为负
理论上 $paymentFee += ($max - total),若 max 远小于 total,paymentFee 可能为负。但实际场景中 max 通常 ≥ total - payment_fee,所以通常不会为负。
6.6 支付方式 A 切换到 B
重新调用 getPaymentFee 走同样的计算链,savePaymentMethod 写入新的 current_payment_price,触发 renew 更新总价。
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
└─ payment_price 在此处被加(正数)
⚡⚡⚡ payment_price 是正数,在 total_price 公式中做加法。
⚠️ 注意:total_price 在这里被 getPaymentFee 引用做基数,然后又由 total_price 包含 payment_price——但通过 基数 = total_price - payment_price 的设计巧妙绕开了循环依赖。
8. 关键代码索引
| 逻辑 | 文件:行号 |
|---|---|
| 入口:getPaymentFee | PaymentService.php:442-513 |
| 基数计算 | PaymentService.php:446 |
| display_param 10+种过滤 | PaymentService.php:452-508 |
| 手续费公式 | PaymentService.php:509-510 |
| lockMaxOrderPrice | PaymentService.php:1663-1717 |
| 封顶修正逻辑 | PaymentService.php:1693-1717 |
| 支付方式列表返回 | PaymentService.php:601-619 |
| 保存已选支付方式 | OrderService::savePaymentMethod |
| 总价重算 | OrderService::renew |