Проблема с распределением стоков при одновременных заказах в WooCommerce
В онлайн-магазинах на WooCommerce часто возникает ситуация, когда несколько покупателей одновременно оформляют заказ на один и тот же товар с ограниченным количеством на складе. Это приводит к тому, что количество доступного стока уменьшается некорректно, и может возникнуть ситуация, когда сумма заказанных товаров превышает фактический запас. В итоге появляются овербукинги (overselling), конфликтные заказы и негативный опыт покупателей.
Причины возникновения проблемы
- Отсутствие блокировки или учета блокировок товаров в момент оформления заказа;
- Встроенная система WooCommerce не учитывает параллельные сессии пользователей;
- Некорректная работа плагинов кэширования, которые кешируют страницы корзины и оформления заказа;
- Особенности хостинга и базы данных, влияющие на скорость записи и обновления данных;
- Отсутствие механизмов резервирования товара на время оформления заказа.
Диагностика проблемы: как понять, что есть рассинхронизация стоков
Для диагностики следует провести следующие проверки:
- Сравнить количество товаров в базе (wp_postmeta с _stock) и фактически доступное после нескольких заказов;
- Проверить логи ошибок сервера и WooCommerce на предмет конфликтов при обновлении запасов;
- Протестировать сценарий оформления параллельных заказов с одного товара, используя разные браузеры или инкогнито режимы;
- Обратить внимание на настройки кэширования (например, WP Rocket, LiteSpeed Cache), исключить из кэширования страницы корзины и оформления заказа;
- Проверить, используются ли сторонние плагины, которые могут влиять на управление запасами.
Пошаговое решение проблемы распределения стоков
1. Отключение кэширования страниц корзины и оформления заказа
Кэширование этих страниц запрещает своевременно обновлять данные о количестве товара в наличии. В настройках кэш-плагина исключите страницы:
- /cart/
- /checkout/
- /my-account/
Пример для WP Rocket (wp-rocket.php):
$options = get_rocket_option('cache_logged_user'); // Отключить кэш для вошедших пользователей
if ( $options ) {
// Добавить исключения страниц
add_filter('rocket_cache_reject_uri', function($uris) {
$uris[] = '/cart/';
$uris[] = '/checkout/';
return $uris;
});
}
2. Реализация блокировки товара на время оформления заказа
Для этого нужно хранить временный резерв товара при добавлении в корзину и освобождать его при отмене или завершении заказа.
Пример кода для блокировки стока при добавлении в корзину:
add_action('woocommerce_add_to_cart', 'reserve_stock_on_add_to_cart', 10, 6);
function reserve_stock_on_add_to_cart($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
$stock = wc_get_stock_quantity($product_id);
$reserved_stock = get_transient('reserved_stock_' . $product_id) ?: 0;
$new_reserved = $reserved_stock + $quantity;
if ($new_reserved > $stock) {
wc_add_notice('Извините, недостаточно товара в наличии для вашего заказа.', 'error');
// Удаляем из корзины
WC()->cart->remove_cart_item($cart_item_key);
return;
}
set_transient('reserved_stock_' . $product_id, $new_reserved, 15 * MINUTE_IN_SECONDS);
}
Важно: при отмене корзины или истечении времени резерв должен сниматься:
add_action('woocommerce_cart_item_removed', 'release_reserved_stock_on_remove', 10, 2);
function release_reserved_stock_on_remove($cart_item_key, $cart) {
$cart_item = $cart->removed_cart_contents[$cart_item_key];
$product_id = $cart_item['product_id'];
$quantity = $cart_item['quantity'];
$reserved_stock = get_transient('reserved_stock_' . $product_id) ?: 0;
$new_reserved = max(0, $reserved_stock - $quantity);
set_transient('reserved_stock_' . $product_id, $new_reserved, 15 * MINUTE_IN_SECONDS);
}
3. Обновление стоков при завершении заказа
При успешном оформлении заказа резерв снимается, а реальные запасы уменьшаются автоматически WooCommerce, но следует убедиться, что резерв очищается:
add_action('woocommerce_order_status_completed', 'clear_reserved_stock_on_order_complete');
function clear_reserved_stock_on_order_complete($order_id) {
$order = wc_get_order($order_id);
foreach ($order->get_items() as $item) {
$product_id = $item->get_product_id();
delete_transient('reserved_stock_' . $product_id);
}
}
Проверка результата после внедрения решения
Чтобы убедиться, что проблема решена, проведите следующие тесты:
- Симультанное добавление товара в корзину с двух и более устройств или браузеров;
- Проверка, что при превышении доступного количества WooCommerce не позволяет добавить больше товара;
- Отслеживание в базе данных значений транзиентов
reserved_stock_*и их корректное обновление; - Оформление и завершение заказа, после чего количество товара уменьшается правильно и резерв очищается;
- Мониторинг логов ошибок и предупреждений отсутствует по связанным действиям.
Частые ошибки и способы их исправления
- Неочищающиеся резервы товара: проверьте, что все хуки снимают резерв при отмене корзины, отмене заказа или истечении времени. Можно добавить CRON-задачу для автоматической очистки устаревших резерваций.
- Конфликты с плагинами кэширования: убедитесь, что страницы корзины и оформления заказа не кэшируются. Если проблемы сохраняются, временно отключите кэш для диагностики.
- Ошибки при одновременном обновлении стоков: проверьте, что база данных корректно обрабатывает транзакции. Если используется внешняя БД или репликация, возможны задержки синхронизации.
- Пользователь может добавить товар в корзину, даже если сток отсутствует: добавьте валидацию при добавлении товара в корзину и перед оформлением заказа.
Практические советы по безопасности и производительности
- Используйте транзиенты с разумным временем жизни для резервирования, чтобы не перегружать базу данных;
- Добавляйте индексы в базу данных по полям, которые часто запрашиваются (например, meta_key = '_stock');
- Регулярно очищайте устаревшие резервы с помощью WP-Cron или внешнего cron-сервиса;
- Убедитесь, что кэширование страниц, связанных с корзиной и оформлением заказа, отключено;
- Проводите нагрузочное тестирование после внедрения, чтобы убедиться, что решение выдерживает пики трафика;
- Рассмотрите возможность использования профессиональных плагинов для управления запасами и резервированием, если бизнес требует более сложных сценариев.
Сравнение вариантов решения проблемы распределения стоков
| Метод | Преимущества | Недостатки |
|---|---|---|
| Реализация резервирования через транзиенты и хуки | Гибкость, контроль над бизнес-логикой, бесплатное решение | Необходимость поддержки и отладки, возможны ошибки при очистке резервов |
| Использование плагинов управления запасами (например, WooCommerce Waitlist, WooCommerce Pre-Orders) | Готовые решения с расширенным функционалом, поддержка разработчиков | Стоимость, возможны конфликты с другими плагинами |
| Отключение параллельных заказов с помощью блокировки сессий | Простота реализации | Плохой UX, ограничение клиентов |