WooCommerce:结合特定商品分类与配送方式限制结账的实现教程
在WooCommerce电商开发中,经常会遇到这样的业务需求:某些特定分类的商品(例如大件家具、冷藏食品、易碎品)只能使用特定的配送方式,或者某些配送方式不能服务于包含特定分类商品的订单。这种限制需要在结账环节动态实现,以确保用户选择的配送方式与购物车中的商品分类相匹配。本文将详细讲解如何通过代码实现这一功能,并提供完整可用的示例。
一、业务场景分析
在实际运营中,常见的限制场景包括以下几种:
- 分类A的商品只能使用"物流配送",不能使用"快递配送"
- 分类B的商品必须使用"冷链配送",其他配送方式不可用
- 当购物车中包含分类C的商品时,隐藏"到店自提"选项
- 混合购物车(包含多个分类商品)时,取配送方式的交集或强制使用最严格的配送方式
二、实现思路
WooCommerce提供了多个钩子函数,允许开发者在结账流程中动态修改可用的配送方式。核心思路是通过 woocommerce_package_rates 过滤器,在计算运费时根据购物车中的商品分类对配送方式进行筛选。具体步骤如下:
- 获取当前购物车中所有商品的分类ID
- 定义每个分类允许的配送方式列表
- 遍历所有可用的配送方式,移除不允许的选项
- 如果所有配送方式都被移除,则提示用户调整购物车
三、基础代码实现
以下是一个完整的代码示例,展示了如何根据商品分类限制配送方式。请将以下代码添加到当前主题的 functions.php 文件中,或者创建一个自定义插件来管理。
<?php
/**
* 根据商品分类限制可用的配送方式
*
* 功能:当购物车中包含特定分类的商品时,只允许使用指定的配送方式
* 作者:WooCommerce开发教程
* 版本:1.0.0
*/
add_filter('woocommerce_package_rates', 'custom_restrict_shipping_by_category', 10, 2);
function custom_restrict_shipping_by_category($rates, $package) {
// 如果没有可用的配送方式,直接返回
if (empty($rates)) {
return $rates;
}
// 获取当前购物车中所有商品的分类ID
$cart_category_ids = array();
foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
$product_id = $cart_item['product_id'];
$terms = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
if (!empty($terms) && !is_wp_error($terms)) {
$cart_category_ids = array_merge($cart_category_ids, $terms);
}
}
$cart_category_ids = array_unique($cart_category_ids);
// 如果没有商品分类,则不做限制
if (empty($cart_category_ids)) {
return $rates;
}
// 定义分类ID与允许配送方式的映射关系
// 键:分类ID(请替换为你实际的分类ID)
// 值:允许的配送方式ID数组
$category_shipping_map = array(
15 => array('flat_rate', 'free_shipping'), // 分类ID 15 只允许"统一运费"和"免费配送"
22 => array('local_pickup'), // 分类ID 22 只允许"到店自提"
37 => array('flat_rate'), // 分类ID 37 只允许"统一运费"
);
// 检查购物车中是否包含受限制的分类
$has_restricted_category = false;
$allowed_shipping_methods = array();
foreach ($cart_category_ids as $cat_id) {
if (isset($category_shipping_map[$cat_id])) {
$has_restricted_category = true;
// 如果当前购物车中有多个受限制的分类,取各分类允许配送方式的交集
if (empty($allowed_shipping_methods)) {
$allowed_shipping_methods = $category_shipping_map[$cat_id];
} else {
$allowed_shipping_methods = array_intersect(
$allowed_shipping_methods,
$category_shipping_map[$cat_id]
);
}
}
}
// 如果没有受限制的商品分类,则不做处理
if (!$has_restricted_category) {
return $rates;
}
// 如果交集为空,说明没有配送方式可以同时满足所有分类的要求
if (empty($allowed_shipping_methods)) {
// 移除所有配送方式,并添加一条提示信息
foreach ($rates as $rate_id => $rate) {
unset($rates[$rate_id]);
}
// 在这里可以添加自定义通知,但通常建议在结账页面显示
return $rates;
}
// 遍历所有配送方式,移除非允许的选项
foreach ($rates as $rate_id => $rate) {
// 从rate_id中提取配送方式ID(例如 flat_rate:2 提取 flat_rate)
$method_id = current(explode(':', $rate_id));
if (!in_array($method_id, $allowed_shipping_methods)) {
unset($rates[$rate_id]);
}
}
return $rates;
}四、代码说明与关键点
上述代码的核心逻辑分为三个部分:获取购物车商品分类、根据映射关系计算允许的配送方式、过滤配送方式列表。下面逐一解释关键点。
4.1 获取购物车商品分类
通过 WC()->cart->get_cart() 获取购物车中的所有商品项,然后使用 wp_get_post_terms() 获取每个商品所属的分类ID。这里使用 product_cat 作为分类法名称,这是WooCommerce商品分类的标准分类法。
4.2 定义分类与配送方式的映射
在 $category_shipping_map 数组中,键是商品分类的ID,值是该分类允许的配送方式ID数组。配送方式ID是WooCommerce内置的标识符,常见的有:
flat_rate:统一运费free_shipping:免费配送local_pickup:到店自提fedex、ups等第三方配送插件注册的ID
4.3 多分类交集的逻辑
当购物车中包含多个受限制的商品分类时,代码使用 array_intersect() 计算各分类允许配送方式的交集。这意味着只有同时被所有受限制分类允许的配送方式才会保留。如果交集为空,则移除所有配送方式。
五、进阶场景:混合购物车的处理策略
在实际业务中,混合购物车(同时包含普通商品和受限分类商品)的处理方式有多种策略。以下展示两种常见的处理方式。
5.1 策略一:受限分类优先
只要购物车中包含受限分类的商品,就严格按照该分类的限制执行。普通分类的商品不受影响,但配送方式的选择受受限分类约束。
<?php
/**
* 策略一:受限分类优先
* 只要购物车中有受限制的分类,就按照该分类的限制来
* 如果有多个受限分类,取交集
*/
add_filter('woocommerce_package_rates', 'strategy_restricted_first', 10, 2);
function strategy_restricted_first($rates, $package) {
// 获取购物车中所有商品的分类ID
$cart_category_ids = array();
foreach (WC()->cart->get_cart() as $cart_item) {
$product_id = $cart_item['product_id'];
$terms = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
if (!empty($terms) && !is_wp_error($terms)) {
$cart_category_ids = array_merge($cart_category_ids, $terms);
}
}
$cart_category_ids = array_unique($cart_category_ids);
// 定义受限分类及其允许的配送方式
$restricted_cats = array(
15 => array('flat_rate'),
22 => array('local_pickup'),
);
// 找出购物车中的受限分类
$found_restricted = array_intersect(array_keys($restricted_cats), $cart_category_ids);
if (empty($found_restricted)) {
return $rates; // 没有受限分类,直接返回
}
// 计算允许的配送方式(取交集)
$allowed = null;
foreach ($found_restricted as $cat_id) {
if (is_null($allowed)) {
$allowed = $restricted_cats[$cat_id];
} else {
$allowed = array_intersect($allowed, $restricted_cats[$cat_id]);
}
}
if (empty($allowed)) {
// 没有共同允许的配送方式,全部移除
return array();
}
// 过滤配送方式
foreach ($rates as $rate_id => $rate) {
$method_id = current(explode(':', $rate_id));
if (!in_array($method_id, $allowed)) {
unset($rates[$rate_id]);
}
}
return $rates;
}5.2 策略二:普通分类不受限制,仅当购物车全为受限分类时生效
这种策略较为宽松:只有当购物车中所有商品都属于受限分类时,才应用分类限制。如果购物车中混有普通分类商品,则所有配送方式都可用。
<?php
/**
* 策略二:仅当购物车全为受限分类时,才应用限制
* 只要包含一个普通分类商品,就放开所有配送方式
*/
add_filter('woocommerce_package_rates', 'strategy_only_all_restricted', 10, 2);
function strategy_only_all_restricted($rates, $package) {
// 定义所有受限分类ID
$restricted_category_ids = array(15, 22, 37);
$cart_category_ids = array();
foreach (WC()->cart->get_cart() as $cart_item) {
$product_id = $cart_item['product_id'];
$terms = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'ids'));
if (!empty($terms) && !is_wp_error($terms)) {
$cart_category_ids = array_merge($cart_category_ids, $terms);
}
}
$cart_category_ids = array_unique($cart_category_ids);
// 检查是否所有商品分类都在受限分类列表中
$all_restricted = true;
foreach ($cart_category_ids as $cat_id) {
if (!in_array($cat_id, $restricted_category_ids)) {
$all_restricted = false;
break;
}
}
// 如果不是全部受限,则不限制配送方式
if (!$all_restricted) {
return $rates;
}
// 全部是受限分类,应用限制规则
// 这里使用与之前相同的映射逻辑
$category_shipping_map = array(
15 => array('flat_rate'),
22 => array('local_pickup'),
37 => array('flat_rate', 'free_shipping'),
);
$allowed_methods = array();
foreach ($cart_category_ids as $cat_id) {
if (isset($category_shipping_map[$cat_id])) {
if (empty($allowed_methods)) {
$allowed_methods = $category_shipping_map[$cat_id];
} else {
$allowed_methods = array_intersect($allowed_methods, $category_shipping_map[$cat_id]);
}
}
}
if (empty($allowed_methods)) {
return array();
}
foreach ($rates as $rate_id => $rate) {
$method_id = current(explode(':', $rate_id));
if (!in_array($method_id, $allowed_methods)) {
unset($rates[$rate_id]);
}
}
return $rates;
}六、在结账页面显示提示信息
当配送方式被限制或全部移除时,给用户明确的提示信息非常重要。以下代码展示了如何在结账页面显示自定义消息。
<?php
/**
* 当配送方式被限制时,在结账页面显示提示信息
*/
add_action('woocommerce_before_checkout_form', 'custom_shipping_restriction_notice', 10);
function custom_shipping_restriction_notice() {
// 检查购物车是否为空
if (WC()->cart->is_empty()) {
return;
}
// 获取购物车中所有商品的分类
$cart_category_ids = array();
foreach (WC()->cart->get_cart() as $cart_item) {
$product_id = $cart_item['product_id'];
$terms = wp_get_post_terms($product_id, 'product_cat', array('fields' => 'names'));
if (!empty($terms) && !is_wp_error($terms)) {
$cart_category_ids = array_merge($cart_category_ids, $terms);
}
}
$cart_category_names = array_unique($cart_category_ids);
// 定义受限分类名称(请替换为你实际的分类名称)
$restricted_cat_names = array('大件家具', '冷链食品', '易碎品');
// 检查是否包含受限分类
$found = array_intersect($cart_category_names, $restricted_cat_names);
if (!empty($found)) {
$category_list = implode('、', $found);
$notice = sprintf(
'您的购物车中包含 %s 分类的商品,根据配送政策,这些商品仅支持特定的配送方式。请在下方选择合适的配送选项。',
$category_list
);
echo '<div class="woocommerce-info">' . esc_html($notice) . '</div>';
}
}七、常见问题与调试方法
在实现过程中,可能会遇到一些问题。以下列出常见问题及解决方法。
| 问题描述 | 可能原因 | 解决方法 |
|---|---|---|
| 配送方式没有按预期变化 | 购物车缓存导致,或分类ID不正确 | 清空购物车重新添加商品;使用 var_dump() 输出分类ID进行验证 |
| 所有配送方式都被移除了 | 分类映射配置错误,交集为空 | 检查 $category_shipping_map 中的配送方式ID是否拼写正确 |
| 代码没有生效 | 钩子优先级问题,或被其他插件覆盖 | 尝试将优先级参数调低,例如 add_filter(..., 1, 2) |
| 提示信息没有显示 | 钩子名称不正确,或分类名称不匹配 | 确认 woocommerce_before_checkout_form 钩子可用;检查分类名称是否一致 |
八、性能优化建议
在每次加载配送方式时都执行分类查询和过滤操作,可能会对性能产生一定影响。以下是几点优化建议:
- 将分类ID映射关系缓存到配置文件中,避免硬编码在函数内部
- 使用WooCommerce的会话缓存机制,避免重复查询数据库
- 对于大型商品分类系统,建议将分类ID预先加载到内存中,减少数据库查询次数
- 可以考虑使用Transient API缓存计算结果,过期时间设置为5分钟
九、总结
通过本文的教程,你已经学会了如何在WooCommerce中根据商品分类来限制可用的配送方式。核心思路是利用 woocommerce_package_rates 过滤器动态修改配送方式列表,并结合购物车中的商品分类信息进行判断。本文提供了两种常见的混合购物车处理策略,以及结账页面的提示信息展示方法。你可以根据实际业务需求,灵活调整分类与配送方式的映射关系,实现更复杂的配送规则。
在实际项目中,建议先在开发环境中测试完整的逻辑流程,确认无误后再部署到生产环境。同时,注意保持代码的可维护性,将分类ID和配送方式ID等配置信息集中管理,方便日后调整和扩展。