WooCommerce: как отключить платные способы доставки при определенном товаре

Сценарий типичный: в магазине есть товар, который нельзя отправлять обычной доставкой, или наоборот — он должен идти только самовывозом. В админке WooCommerce это часто пытаются решить зонами доставки, но как только в корзине появляется конкретный товар, правила начинают конфликтовать: часть методов остается видимой, часть исчезает не там, где нужно, а иногда ломается пересчет доставки в AJAX-корзине.

Ниже — рабочий способ без выдуманных хуков и без тяжелых плагинов: через фильтр woocommerce_package_rates. Он позволяет убрать нужные способы доставки на этапе расчета, когда WooCommerce уже знает содержимое корзины и список доступных тарифов.

Когда проблема проявляется на практике

Обычно ошибка выглядит так:

  • в корзине есть товар с особым условием, но платная доставка все равно показывается;
  • метод доставки скрывается только на странице оформления, но остается в мини-корзине или после AJAX-обновления;
  • правило срабатывает для всех товаров в зоне, хотя нужно только для одного SKU или ID;
  • после добавления кода доставка перестает пересчитываться или появляется пустой список методов.

Если у вас уже настроены зоны доставки, сначала проверьте, не проще ли решить задачу на уровне зон и классов доставки. Но если условие зависит именно от состава корзины, фильтр по тарифам обычно надежнее.

Диагностика: что проверить до правки кода

Перед внедрением решения полезно понять, где именно ломается логика. Это экономит время, особенно если на сайте есть кэш, сторонние плагины доставки или кастомные правила.

Проверьте, как идентифицируется товар

Нужно заранее решить, по чему вы будете определять проблемный товар: по ID, по SKU, по категории или по классу доставки. Для точечного сценария лучше использовать ID или SKU, потому что они меньше зависят от структуры каталога.

Проверьте, какие методы доставки реально доступны

В WooCommerce методы доставки в коде выглядят не как названия из интерфейса, а как идентификаторы вроде flat_rate:3, free_shipping:1, local_pickup:2. Если удалить не тот ключ, визуально ничего не изменится.

Для диагностики удобно временно вывести список тарифов в лог:

add_filter('woocommerce_package_rates', function ($rates, $package) {
    if (defined('WP_DEBUG') && WP_DEBUG) {
        error_log(print_r(array_keys($rates), true));
    }
    return $rates;
}, 10, 2);

После этого откройте оформление заказа с включенным WP_DEBUG_LOG и посмотрите, какие ключи реально приходят в массиве.

Решение: скрываем платную доставку для конкретного товара

Ниже пример, который отключает все платные методы доставки, если в корзине есть товар с указанным ID. Самовывоз при этом остается доступным.

add_filter('woocommerce_package_rates', function ($rates, $package) {
    if (is_admin() && !defined('DOING_AJAX')) {
        return $rates;
    }

    $blocked_product_ids = array(123); // ID товара, который требует особых условий
    $has_blocking_product = false;

    foreach ($package['contents'] as $item) {
        if (!empty($item['product_id']) && in_array((int) $item['product_id'], $blocked_product_ids, true)) {
            $has_blocking_product = true;
            break;
        }
    }

    if (!$has_blocking_product) {
        return $rates;
    }

    foreach ($rates as $rate_id => $rate) {
        if (strpos($rate_id, 'local_pickup') === 0) {
            continue;
        }

        unset($rates[$rate_id]);
    }

    return $rates;
}, 20, 2);

Логика простая: если в пакете есть нужный товар, мы оставляем только local_pickup. Все остальные методы удаляются из массива тарифов до вывода на фронтенд.

Если нужно скрывать только отдельные платные методы

Иногда самовывоз тоже не нужен, а убрать надо только курьерку или фиксированную ставку. Тогда фильтруйте по префиксу метода:

add_filter('woocommerce_package_rates', function ($rates, $package) {
    $target_product_id = 123;
    $found = false;

    foreach ($package['contents'] as $item) {
        if ((int) $item['product_id'] === $target_product_id) {
            $found = true;
            break;
        }
    }

    if (!$found) {
        return $rates;
    }

    foreach ($rates as $rate_id => $rate) {
        if (strpos($rate_id, 'flat_rate') === 0) {
            unset($rates[$rate_id]);
        }
    }

    return $rates;
}, 20, 2);

Это удобнее, если у вас несколько зон и разные flat_rate для разных регионов: удаляются все тарифы с этим типом, независимо от ID.

Если условие зависит не от одного товара, а от категории

Для магазина с большим каталогом часто проще управлять правилом через категорию. Например, все хрупкие товары должны идти только самовывозом.

add_filter('woocommerce_package_rates', function ($rates, $package) {
    $blocked_category_slug = 'fragile';
    $has_blocked_category = false;

    foreach ($package['contents'] as $item) {
        $product_id = (int) $item['product_id'];
        if (has_term($blocked_category_slug, 'product_cat', $product_id)) {
            $has_blocked_category = true;
            break;
        }
    }

    if (!$has_blocked_category) {
        return $rates;
    }

    foreach ($rates as $rate_id => $rate) {
        if (strpos($rate_id, 'free_shipping') === 0) {
            continue;
        }
        unset($rates[$rate_id]);
    }

    return $rates;
}, 20, 2);

Такой вариант особенно полезен, если ассортимент меняется и привязываться к ID неудобно.

Сравнение подходов

ПодходКогда подходитМинус
Зоны доставки WooCommerceПравило зависит от региона, а не от товараСложно учесть состав корзины
woocommerce_package_ratesНужно скрыть методы по товару, категории или составу корзиныТребует аккуратной проверки ID тарифов
Плагин с правилами доставкиНужен интерфейс без кода и много условийМожет конфликтовать с темой и другими расширениями

Пошаговая настройка без сюрпризов

  1. Определите, по чему срабатывает правило: ID товара, SKU или категория.
  2. Включите логирование на тестовом сайте или в staging-окружении.
  3. Добавьте фильтр woocommerce_package_rates в дочернюю тему или мини-плагин.
  4. Проверьте, какие ключи у методов доставки приходят в массиве $rates.
  5. Удалите только те тарифы, которые действительно нужно скрыть.
  6. Протестируйте оформление заказа с одним товаром, с несколькими товарами и с пустой корзиной.

Как проверить, что решение сработало

Проверка должна быть не только визуальной. В WooCommerce часть ошибок проявляется после AJAX-пересчета или при смене адреса доставки.

  • Добавьте в корзину только целевой товар и убедитесь, что нужные методы исчезли.
  • Добавьте обычный товар вместе с целевым и проверьте, что правило все еще работает.
  • Смените адрес доставки, если у вас есть зоны по регионам, и посмотрите, не возвращаются ли скрытые методы.
  • Обновите корзину через AJAX и проверьте, что список методов не «откатывается».
  • Откройте заказ в админке и убедитесь, что доставка не подставляется вручную в обход правила.

Если нужно быстро убедиться, что условие вообще срабатывает, временно добавьте в лог сообщение внутри блока if:

error_log('Shipping rule matched for product ID 123');

После теста этот код лучше убрать, чтобы не засорять лог.

Частые ошибки и как их исправить

Удаляют методы по названию, а не по ID

В интерфейсе WooCommerce вы видите «Фиксированная ставка», но в коде это может быть flat_rate:3. Если удалить только flat_rate без проверки, можно случайно задеть не тот тариф. Сначала выведите массив ключей и проверьте реальные значения.

Пишут условие только для $_POST или только для страницы оформления

Доставка пересчитывается не только на странице checkout. Если правило завязано на неправильный контекст, оно будет вести себя нестабильно при AJAX-обновлении. Фильтр woocommerce_package_rates как раз удобен тем, что работает на этапе расчета тарифа.

Проверяют только product_id, но в корзине есть вариации

Если товар вариативный, иногда важен родительский товар, а иногда конкретная вариация. В зависимости от задачи может понадобиться проверка variation_id или родительского product_id. Это нужно тестировать на реальной корзине, а не по одному примеру.

Код кладут в активную тему без резервной копии

Если тема обновится или сменится, правило пропадет. Для стабильности лучше вынести логику в небольшой mu-plugin или хотя бы в отдельный мини-плагин. Так проще отключить его при конфликте и не потерять настройку при обновлении темы.

Безопасность и производительность

Сам фильтр легкий, но есть несколько практических моментов:

  • не делайте тяжелые запросы к базе внутри цикла по товарам корзины;
  • если проверяете категорию, не вызывайте лишние функции на каждом тарифе;
  • не логируйте массивы тарифов на боевом сайте постоянно;
  • не скрывайте методы через JavaScript на фронтенде — это только маска, а не реальное правило;
  • если сайт под кэшем, убедитесь, что страница оформления не кэшируется агрессивно.

Если у вас много правил доставки и сложная логика, иногда выгоднее вынести часть условий в отдельный плагин с интерфейсом. Но для точечного сценария «один товар — один набор методов» код через woocommerce_package_rates обычно проще и надежнее.

Когда нужно быстро проверить, не конфликтует ли правило с другими настройками магазина, удобно временно отключить лишние плагины доставки и сравнить поведение. Если после этого проблема исчезает, значит, конфликт не в коде, а в пересечении нескольких правил расчета.

WooCommerce: решение проблемы с неотображением атрибутов товара в корзине
01.06.2026
WooCommerce: решение проблем с отображением товаров и подсказками в корзине
19.06.2026
Как создать эффективные методы фильтрования данных в WordPress для больших таблиц
18.12.2025
Как создать эффективный индекс по метаданным в WordPress для ускорения запросов
04.01.2026
WooCommerce: как отключить платные способы доставки при определенном товаре
08.08.2026