关键钩子与代码骨架:Bundle 配置、加购校验、动态计价
WooCommerce Product Bundles 的底层数据模型与价格链路
WooCommerce Product Bundles(产品捆绑)本质上是“父商品 + 子商品集合”的复合产品类型。底层实现通常不是简单把几个商品拼在一起展示,而是扩展 WooCommerce 的 product_type 与购物车/订单计算流程,让一个 bundle 订单能携带多个子项,并且在库存、折扣、税费、运费上保持一致。
在数据层,插件会在父商品(bundle)上保存一组“绑定子商品”配置,常见形式是 post_meta 数组:子商品 ID、默认数量、最小/最大数量、是否可选、折扣类型(固定/百分比)、定价模式(父价/子价叠加/动态价)等。前端渲染时,通过读取这些 meta 生成一个 bundle schema,再输出到模板中。用户选项变化会驱动 JS 重新计算价格预估,但最终权威价格仍由服务器端重新汇总(防止篡改)。
价格链路上,bundle 插件一般会挂钩:
- 加入购物车前校验:检查每个子项的数量范围、必选项是否存在、变体是否可用;
- 购物车价格重算:在
woocommerce_before_calculate_totals中遍历 bundle 父行项目,按配置动态生成子项价格并写入 cart item data; - 下单后拆解写入订单: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-&gt;get_price() * $child['qty'];
// 折扣示例
if ($child['conf']['type'] === 'percent'){
$price *= (1 - $child['conf']['discount']/100);
}
$total += $price;
}
// 动态价模式:父价=子项汇总
$parent-&gt;set_price($total);
}
});
这个骨架体现了三件事:
- Bundle 不是“展示组合”,而是“复合产品类型”,子项配置必须进入 cart data;
- JS 只是预览,真正支付价格一定在服务端根据 bundle_items 重算;
- 订单层要保留父子关系(真实插件会在下单钩子里把子项写入 order item meta,保证库存扣减和退款追踪正确)。
如果你的业务需要“可选子项”“动态定价”“统一库存规则”,Product Bundles 的这种底层设计能避免手工组合带来的价格漂移、库存错扣和售后不可追溯的问题。
评论 0