Типичный сценарий: в магазине есть товар, который нельзя отправлять обычной доставкой, либо он требует отдельной логистики. В корзине при его добавлении нужно скрыть часть способов доставки — например, курьера, самовывоз или платные тарифы, оставив только подходящий вариант. Делать это вручную через настройки неудобно: правило должно срабатывать автоматически и только для нужных товаров.
Ниже — рабочий вариант без выдуманных хуков и без лишней магии. Логика строится на фильтре WooCommerce woocommerce_package_rates: он позволяет отфильтровать доступные методы доставки до вывода на checkout.
Когда это действительно нужно
Чаще всего задача возникает в таких случаях:
- в корзине есть крупногабаритный товар, который нельзя отправить частью стандартных тарифов;
- один товар доступен только через самовывоз или отдельную службу доставки;
- для определённой категории товаров нужно убрать бесплатную доставку, чтобы не было ошибочного выбора;
- в магазине смешиваются обычные и особые товары, а правила доставки должны зависеть от состава корзины.
Если у вас просто один тариф стал недоступен из-за настроек зоны доставки, это другая проблема. Здесь речь именно о динамическом скрытии методов в зависимости от содержимого корзины.
Диагностика проблемы перед правкой кода
Сначала проверьте, что именно вы хотите скрывать и по какому признаку. Ошибка на этом этапе обычно приводит к тому, что доставка исчезает у всех заказов или, наоборот, не отключается вовсе.
Что нужно выяснить
- какой товар является триггером: конкретный ID, категория, тег или метаполе;
- какие методы доставки нужно скрыть:
flat_rate,free_shipping,local_pickupили кастомный способ; - в каких зонах доставки это должно работать;
- нужно ли правило для всей корзины или только если товар присутствует хотя бы в одной позиции.
Чтобы не гадать, откройте страницу оформления заказа с тестовой корзиной и посмотрите, какие методы реально возвращает WooCommerce. Если у вас включён кэш на уровне страницы checkout, сначала отключите его для этой страницы — иначе вы будете проверять не код, а сохранённый HTML.
Решение через фильтр woocommerce_package_rates
Самый надёжный способ — добавить небольшой фрагмент в functions.php дочерней темы или в собственный мини-плагин. Лучше второй вариант: код не потеряется при смене темы.
Пример ниже скрывает все платные методы доставки, если в корзине есть товар с указанным ID. При необходимости вы можете заменить проверку на категорию.
add_filter( 'woocommerce_package_rates', 'wpstudy_hide_shipping_rates_for_specific_product', 10, 2 );
function wpstudy_hide_shipping_rates_for_specific_product( $rates, $package ) {
if ( is_admin() && ! wp_doing_ajax() ) {
return $rates;
}
$target_product_id = 123; // ID товара, который влияет на доставку
$found = false;
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( (int) $cart_item['product_id'] === $target_product_id ) {
$found = true;
break;
}
}
if ( ! $found ) {
return $rates;
}
foreach ( $rates as $rate_id => $rate ) {
// Оставляем только самовывоз, всё остальное скрываем
if ( 'local_pickup' !== $rate->method_id ) {
unset( $rates[ $rate_id ] );
}
}
return $rates;
}Если нужно скрывать методы не по ID товара, а по категории, логика почти та же. Проверка меняется на has_term():
add_filter( 'woocommerce_package_rates', 'wpstudy_hide_shipping_rates_for_category', 10, 2 );
function wpstudy_hide_shipping_rates_for_category( $rates, $package ) {
if ( is_admin() && ! wp_doing_ajax() ) {
return $rates;
}
$target_category = 'oversized';
$found = false;
foreach ( WC()->cart->get_cart() as $cart_item ) {
$product_id = $cart_item['product_id'];
if ( has_term( $target_category, 'product_cat', $product_id ) ) {
$found = true;
break;
}
}
if ( ! $found ) {
return $rates;
}
foreach ( $rates as $rate_id => $rate ) {
if ( 'local_pickup' !== $rate->method_id ) {
unset( $rates[ $rate_id ] );
}
}
return $rates;
}Если нужно скрыть только один конкретный тариф
Иногда задача проще: не убирать все платные способы, а скрыть только один метод, например flat_rate:3. Тогда ориентируйтесь на полный ID тарифа, а не только на method_id.
add_filter( 'woocommerce_package_rates', 'wpstudy_hide_one_shipping_rate', 10, 2 );
function wpstudy_hide_one_shipping_rate( $rates, $package ) {
$target_product_id = 123;
$hide_rate_id = 'flat_rate:3';
$found = false;
foreach ( WC()->cart->get_cart() as $cart_item ) {
if ( (int) $cart_item['product_id'] === $target_product_id ) {
$found = true;
break;
}
}
if ( $found && isset( $rates[ $hide_rate_id ] ) ) {
unset( $rates[ $hide_rate_id ] );
}
return $rates;
}Какой вариант выбрать: код, плагин или настройка зоны
Если правило одно и оно завязано на состав корзины, код обычно проще и прозрачнее. Если правил много и они меняются менеджером без разработчика, имеет смысл смотреть в сторону плагина с условиями доставки. Но не стоит ставить тяжёлое решение ради одной проверки.
| Подход | Когда подходит | Минус |
|---|---|---|
Код через woocommerce_package_rates | Нужно точечное правило по товару или категории | Требует поддержки в теме или мини-плагине |
| Настройки зон доставки | Методы зависят только от региона | Не учитывает состав корзины |
| Плагин с условиями доставки | Много правил, ими управляет контент-менеджер | Дополнительная нагрузка и зависимость от плагина |
Если у вас уже стоит плагин для очистки и оптимизации WooCommerce, например Clearfy Pro, часть лишних дублей и служебных элементов можно убрать отдельно, но саму логику доставки лучше оставить в коде — так она будет предсказуемее.
Проверка результата после внедрения
После добавления кода не ограничивайтесь визуальной проверкой на одной корзине. Нужно пройти несколько сценариев.
- добавьте в корзину только обычный товар и убедитесь, что все методы доставки на месте;
- добавьте только товар-триггер и проверьте, что скрытые методы исчезли;
- добавьте обычный товар вместе с проблемным и проверьте смешанную корзину;
- смените адрес доставки, если правило зависит от зоны;
- очистите кэш страницы оформления заказа и кэш плагина, если он есть.
Для быстрой диагностики можно временно вывести список доступных методов в лог. Не оставляйте такой код на продакшене надолго, но для проверки он полезен.
add_filter( 'woocommerce_package_rates', 'wpstudy_log_shipping_rates', 99, 2 );
function wpstudy_log_shipping_rates( $rates, $package ) {
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( 'Shipping rates: ' . print_r( array_keys( $rates ), true ) );
}
return $rates;
}Если в логе нужный тариф есть, но на checkout он не отображается, проблема уже не в фильтре, а в кэше, конфликте с другим плагином доставки или в настройках зоны.
Частые ошибки и как их исправить
Проверяют не тот ID
У товара есть product_id, а у вариации — отдельный variation_id. Если в корзине вариативный товар, а вы сравниваете только с ID родительского товара, правило может не сработать. В таком случае проверьте и product_id, и variation_id.
Скрывают методы в админке
Если не добавить защиту is_admin() и wp_doing_ajax(), фильтр может мешать служебным запросам WooCommerce. Это особенно заметно при обновлении корзины и на AJAX-операциях checkout.
Сравнивают только method_id
У нескольких тарифов может быть одинаковый method_id, но разные экземпляры. Если нужно убрать один конкретный способ, используйте полный ID вроде flat_rate:3.
Код кладут в родительскую тему
После обновления темы правило исчезнет. Для таких задач безопаснее мини-плагин или дочерняя тема.
Не очищают кэш
Если checkout кэшируется сторонним плагином или сервером, вы можете долго видеть старое поведение. После правки сбросьте кэш страницы, объектный кэш и кэш CDN, если он используется.
Практические советы по безопасности и производительности
Сам фильтр лёгкий, но есть несколько моментов, которые стоит учесть:
- не делайте сложные запросы к базе внутри цикла по корзине;
- если правило завязано на категории, используйте
has_term(), а не отдельные запросы черезWP_Query; - не храните ID товара в открытом виде в нескольких местах — вынесите в константу или настройку мини-плагина;
- если логика доставки сложная, разделите её на несколько маленьких функций, а не на один большой блок с десятком условий.
Когда правило начинает разрастаться, лучше вынести его в отдельный плагин для магазина. Это проще сопровождать, чем держать логику в шаблоне и потом искать её после обновления темы.
Если нужно не просто скрывать методы, а строить более сложные сценарии для магазина, иногда удобнее использовать специализированные блоки и расширения для WooCommerce. Но для точечного ограничения доставки по товару кодовый вариант обычно быстрее, прозрачнее и надёжнее.