
🛒 Дубли заказов в WooCommerce: 3 скрипта для блокировки на уровне корзины
Клиент нажал «Оформить заказ» дважды, и вот уже в админке два одинаковых заказа с разницей в пару секунд. Деньги списаны дважды, клиент в ярости, а вы разбираете refund.
Дубли заказов, не гипотетическая ситуация. С ними сталкивается примерно каждый третий магазин на WooCommerce, особенно при нестабильном хостинге или медленных платёжных шлюзах. Но даже на быстром сервере пользователь может добавить товар через URL повторно или купить бесплатный цифровой продукт десятки раз.
Мы собрали три рабочих скрипта, которые закрывают разные сценарии дублирования: от принудительной продажи по одной единице до блокировки повторных URL-добавлений. Ниже, полный разбор с кодом.
Почему дублируются заказы в WooCommerce
💡 Быстрый обзор:
- Узнаете, почему заказы дублируются на уровне корзины, URL и повторных покупок
- Поставите скрипт для принудительной продажи товара только по одной единице через мета-поля
- Запретите повторную покупку бесплатных вариаций (цифровые товары, купоны)
- Уберёте дублирование через URL с
add-to-cartпри обновлении страницы - Проверите защиту и узнаете, что ещё может вызывать дубли на уровне сервера
Дубли заказов возникают по трём основным сценариям.
Первый, пользователь несколько раз кликает «Оформить заказ». Такое бывает, когда страница тормозит, а человек не видит реакции и жмёт кнопку снова. На медленном хостинге, регулярная история.
Второй сценарий, бесплатные товары. Если у вас есть бесплатный цифровой продукт, ничто не мешает одному пользователю «купить» его десять раз подряд. WooCommerce из коробки не отслеживает, брал ли этот клиент такой же бесплатный товар раньше.
Третий, прямая ссылка с параметром ?add-to-cart=123. Обновил страницу корзины, товар добавился снова. Перешёл по ссылке из рассылки дважды, в корзине два экземпляра.
Все три сценария решаются на стороне сервера через хуки WooCommerce. Никаких плагинов, только код.
Способ 1: Принудительная продажа по одной единице через мета-поля

Первый скрипт заставляет WooCommerce считать конкретный товар «продаваемым поштучно», даже если в настройках товара эта галочка не стоит. Управление, через мета-поле.
Когда meta-поле maximum_allowed_quantity равно 1, фильтр woocommerce_is_sold_individually возвращает true, и WooCommerce сам блокирует добавление второй единицы в корзину. Работает и для простых товаров, и для вариаций.
Добавьте код в functions.php дочерней темы или через плагин Code Snippets:
1 function sd_woocommerce_is_sold_individually( $return, $product ) { 2 switch ( $product->get_type() ) { 3 case 'variation': 4 if ( $product->get_meta( 'variation_maximum_allowed_quantity' ) == 1 ) { 5 return true; 6 } 7 break; 8 case 'simple': 9 if ( $product->get_meta( 'maximum_allowed_quantity' ) == 1 ) { 10 return true; 11 } 12 break; 13 } 14 return $return; 15 } 16 add_filter( 'woocommerce_is_sold_individually', 'sd_woocommerce_is_sold_individually', 10, 2 );
Что здесь происходит: хук woocommerce_is_sold_individually срабатывает каждый раз, когда WooCommerce проверяет, можно ли добавить товар в корзину больше одного раза. Функция заглядывает в meta-поля товара и, если видит maximum_allowed_quantity = 1, принудительно включает режим «Sold Individually». Для вариаций, поле называется variation_maximum_allowed_quantity.
Как применить. Зайдите в редактирование любого товара или вариации, добавьте custom field с именем maximum_allowed_quantity (или variation_maximum_allowed_quantity для вариации) и значением 1. Сохраните. Всё, теперь этот товар нельзя положить в корзину дважды.
Способ 2: Запрет повторной покупки бесплатных вариаций
Бесплатные цифровые товары, главная брешь в защите от дублей. Пользователь авторизован, цена товара равна нулю, WooCommerce пропускает заказ без вопросов. Десять таких заказов от одного клиента, десять одинаковых записей в админке.
Скрипт ниже проверяет через wc_customer_bought_product(), покупал ли этот пользователь такой же товар раньше. Если да, добавление в корзину блокируется с понятным уведомлением.
1 function sd_disable_repeat_purchase_for_free_products( $passed, $product_id, $quantity, $variation_id = 0 ) { 2 $current_user = wp_get_current_user(); 3 $product = $variation_id > 0 ? wc_get_product( $variation_id ) : wc_get_product( $product_id ); 4 $target_id = $variation_id > 0 ? $variation_id : $product_id; 5 6 if ( floatval( $product->get_price() ) == 0 ) { 7 if ( wc_customer_bought_product( $current_user->user_email, $current_user->ID, $target_id ) ) { 8 wc_add_notice( 9 sprintf( __( 'You have already purchased "%s" and cannot purchase it again.', 'sd' ), $product->get_name() ), 10 'error' 11 ); 12 return false; 13 } 14 } 15 return $passed; 16 } 17 add_filter( 'woocommerce_add_to_cart_validation', 'sd_disable_repeat_purchase_for_free_products', 10, 4 );
После установки пользователь видит сообщение об ошибке при попытке добавить в корзину уже купленный бесплатный товар. Сам заказ не создаётся.
Два важных условия. Первое, клиент должен быть авторизован. Для гостей wp_get_current_user() возвращает пустой объект, и проверка не сработает. Второе, цена товара должна быть ровно ноль (floatval исключает проблемы с форматированием). Товары с ценой выше нуля скрипт не затронет.
Способ 3: Защита от дублирования через URL с add-to-cart
WooCommerce позволяет добавить товар в корзину по прямой ссылке: /cart/?add-to-cart=123. Удобно для маркетинга, но если пользователь обновит страницу или перейдёт по ссылке повторно, товар добавится снова.
Этот скрипт перехватывает загрузку страницы корзины или чекаута и, если товар из URL-параметра уже лежит в корзине, молча убирает query-параметры и редиректит на чистый URL.
1 function sd_prevent_duplicate_add_to_cart_redirect() { 2 if ( ( is_cart() || is_checkout() ) && isset( $_GET['add-to-cart'] ) ) { 3 $current_url = home_url( add_query_arg( null, null ) ); 4 $product_id = intval( $_GET['add-to-cart'] ); 5 $variation_id = isset( $_GET['variation_id'] ) ? intval( $_GET['variation_id'] ) : 0; 6 7 foreach ( WC()->cart->get_cart() as $cart_item ) { 8 if ( $cart_item['product_id'] == $product_id 9 && ( $variation_id == 0 || $cart_item['variation_id'] == $variation_id ) ) { 10 wp_redirect( remove_query_arg( array( 'add-to-cart', 'variation_id' ), $current_url ) ); 11 exit; 12 } 13 } 14 } 15 } 16 add_action( 'template_redirect', 'sd_prevent_duplicate_add_to_cart_redirect' );
Хук template_redirect срабатывает до того, как WordPress начнёт рендерить страницу. Скрипт проверяет, есть ли в URL параметр add-to-cart, ищет этот товар в содержимом корзины (WC()->cart->get_cart()), и если находит, делает wp_redirect на ту же страницу без query-параметров.
Повторное добавление через URL исключено. При этом первое добавление (когда товара в корзине ещё нет) работает как обычно.
Собираем всё вместе
Все три скрипта не конфликтуют и работают независимо:
- Первый ограничивает количество единиц одного товара до 1, защита от множественного добавления.
- Второй блокирует повторную покупку бесплатных продуктов, защита от «накликивания» халявы.
- Третий убирает дублирование через URL, защита от случайных повторных добавлений.
Добавляйте код через плагин Code Snippets на WP.org, а не прямо в functions.php темы. Плагин позволяет включать и отключать сниппеты по одному, не ломая сайт при ошибке, и не теряет код при смене темы. Создайте три отдельных сниппета, так проще отлаживать.
Что ещё проверить, если дубли сохраняются
Скрипты выше закрывают дубли на уровне корзины и оформления. Но есть ещё два источника проблемы, которые кодом не исправить.
Кэширование страниц корзины и чекаута. Плагины кэширования вроде WP Rocket или LiteSpeed Cache по умолчанию исключают эти страницы, но после обновления настроек exclusion может сброситься. Проверьте: cart, checkout и my-account не должны кэшироваться ни на уровне плагина, ни на уровне сервера, ни в CDN.
Повторы со стороны платёжного шлюза. Если шлюз (Stripe, PayPal) не получает от сервера подтверждение вовремя, он отправляет уведомление повторно, и WooCommerce создаёт второй заказ. Причина обычно в медленном хостинге или firewall, блокирующем callback-запросы. Решение: проверьте логи шлюза на предмет повторных webhook-отправок и убедитесь, что callback-URL доступен снаружи.
Как протестировать защиту
После установки скриптов обязательно проверьте каждый сценарий на тестовом сайте.
Способ 1, создайте товар с meta-полем maximum_allowed_quantity = 1 и попробуйте добавить две единицы в корзину. Должна добавиться только одна.
Способ 2, авторизуйтесь, купите бесплатный товар, затем попробуйте купить его снова. Должны увидеть сообщение об ошибке.
Способ 3, добавьте товар в корзину через прямую ссылку с ?add-to-cart=ID, затем обновите страницу. Количество позиций в корзине не должно измениться.
Тестируйте в отдельной инсталляции или на staging-сайте. На живом магазине, только после полной проверки.
⁉️🤔 Частые вопросы
Почему дублируются заказы, если кнопка «Оформить» нажата один раз?
Причина почти всегда в медленном ответе сервера. Пользователь нажимает кнопку, запрос уходит, но страница не обновляется несколько секунд. Человек думает, что не нажал, и кликает снова. Оба запроса доходят до WooCommerce, и создаются два заказа. Скрипты из этой статьи такую ситуацию не ловят, здесь помогает кэширование на стороне клиента (disable button after click) или серверная проверка уникальности заказа по nonce-токену.
Может ли платёжный шлюз создавать дубли сам по себе?
Да. Если шлюз не получает от вашего сервера HTTP 200 в ответ на IPN/webhook, он повторяет отправку. При каждом повторе WooCommerce может создать новый заказ. Проблема решается ускорением хостинга и проверкой, что callback-URL шлюза не заблокирован файрволом и не требует HTTP-авторизации.
Блокирует ли эта защита настоящие повторные заказы от клиента?
Нет. Скрипты отрабатывают на этапе добавления в корзину, а не на этапе создания заказа. Если клиент осознанно оформил один заказ, а через неделю, второй, скрипты не вмешаются. Исключение, способ 2, который запрещает повторную покупку бесплатного товара тем же пользователем.
Обязательно ли использовать Code Snippets, или можно в functions.php?
Можно и в
functions.php, но мы советуем Code Snippets. При опечатке в коде functions.php вы получите белый экран и доступ только через FTP. Плагин Code Snippets ловит фатальные ошибки и автоматически отключает проблемный сниппет, сайт остаётся на ногах.
Помогают ли плагины для предотвращения дублей?
Специализированные плагины вроде WooCommerce Duplicate Order Blocker работают на другом уровне, они отслеживают дубли по IP, email или отпечатку браузера на этапе оформления. Это закрывает сценарий двойного клика по кнопке, который наши скрипты не покрывают. При этом плагины не решают проблему дублей через add-to-cart URL и повторных покупок бесплатного товара. Идеальный вариант, скрипты из этой статьи плюс плагин-блокировщик на чекауте.
Итоги: три уровня защиты от дублей
Три скрипта закрывают три конкретных сценария дублирования заказов. Поставьте все сразу, каждый отвечает за свой участок, и вместе они дают плотную защиту на уровне корзины.
Если после установки дубли всё ещё появляются, смотрите в сторону хостинга и платёжного шлюза. Двойной клик по кнопке «Оформить» кодом на уровне корзины не лечится, здесь нужен либо плагин-блокировщик, либо кастомная защита кнопки через JavaScript.
Проверьте защиту на тестовом сайте, и переносите на боевой. Пятнадцать минут на установку трёх сниппетов экономят часы разбора refund-ов.


