关键钩子与代码骨架:Bundle 配置、加购校验、动态计价

WooCommerce Product Bundles 的底层数据模型与价格链路

WooCommerce Product Bundles(产品捆绑)本质上是“父商品 + 子商品集合”的复合产品类型。底层实现通常不是简单把几个商品拼在一起展示,而是扩展 WooCommerce 的 product_type 与购物车/订单计算流程,让一个 bundle 订单能携带多个子项,并且在库存、折扣、税费、运费上保持一致。 在数据层,插件会在父商品(bundle)上保存一组“绑定子商品”配置,常见形式是 post_meta 数组:子商品 ID、默认数量、最小/最大数量、是否可选、折扣类型(固定/百分比)、定价模式(父价/子价叠加/动态价)等。前端渲染时,通过读取这些 meta 生成一个 bundle schema,再输出到模板中。用户选项变化会驱动 JS 重新计算价格预估,但最终权威价格仍由服务器端重新汇总(防止篡改)。

价格链路上,bundle 插件一般会挂钩:

  1. 加入购物车前校验:检查每个子项的数量范围、必选项是否存在、变体是否可用;
  2. 购物车价格重算:在 woocommerce_before_calculate_totals 中遍历 bundle 父行项目,按配置动态生成子项价格并写入 cart item data;
  3. 下单后拆解写入订单:order line items 中保留父项,同时把子项以“关联行项目/元数据”形式附着,便于库存与售后。


关键钩子与代码骨架:Bundle 配置、加购校验、动态计价

下面用一个“简化版 bundle 核心流程”示例说明 Product Bundles 的底层思路(关键点与真实插件一致):

// 1) 保存 bundle 子项配置(后台自定义面板略)
function get_bundle_items($bundle_id){
  // meta: [ ['id'=>123,'min'=>1,'max'=>5,'discount'=>10,'type'=>'percent'], ... ]
  return (array) get_post_meta($bundle_id, '_bundle_items', true);
}

// 2) 加入购物车时注入 bundle 选择 add_filter('woocommerce_add_cart_item_data', function($cart_item_data, $product_id){ if (get_post_type($product_id) !== 'product') return $cart_item_data;

$product = wc_get_product($product_id); if ($product->get_type() !== 'bundle') return $cart_item_data;

$items = get_bundle_items($product_id); $chosen = [];

foreach ($items as $it){ $qty = isset($_POST['bundle_qty'][$it['id']]) ? intval($_POST['bundle_qty'][$it['id']]) : 0; if ($qty < $it['min'] || $qty > $it['max']) { wc_add_notice('Bundle item quantity invalid.', 'error'); return false; } if ($qty > 0) $chosen[] = ['id'=>$it['id'], 'qty'=>$qty, 'conf'=>$it]; }

$cart_item_data['bundle_items'] = $chosen; // 写入 cart data return $cart_item_data; }, 10, 2);

// 3) 购物车动态计价(服务端权威) add_action('woocommerce_before_calculate_totals', function($cart){ foreach ($cart->get_cart() as $key => $item){ if (empty($item['bundle_items'])) continue;

$parent = $item['data'];
$total = 0;

foreach ($item['bundle_items'] as $child){
  $p = wc_get_product($child['id']);
  $price = (float) $p->get_price() * $child['qty'];

  // 折扣示例
  if ($child['conf']['type'] === 'percent'){
    $price *= (1 - $child['conf']['discount']/100);
  }
  $total += $price;
}

// 动态价模式:父价=子项汇总
$parent->set_price($total);

} });

这个骨架体现了三件事:

  • Bundle 不是“展示组合”,而是“复合产品类型”,子项配置必须进入 cart data;
  • JS 只是预览,真正支付价格一定在服务端根据 bundle_items 重算;
  • 订单层要保留父子关系(真实插件会在下单钩子里把子项写入 order item meta,保证库存扣减和退款追踪正确)。

如果你的业务需要“可选子项”“动态定价”“统一库存规则”,Product Bundles 的这种底层设计能避免手工组合带来的价格漂移、库存错扣和售后不可追溯的问题。

评论 0