Оформление B2B-заказа при нехватке товара
Задача
В B2B-магазине покупатель может заказать больше товара, чем доступно сейчас. Одну часть нужно отправить сразу, другую — оформить как отложенную поставку. При этом итог корзины и платёж должны остаться понятными покупателю, а складской остаток меняется, пока несколько клиентов собирают заказы. К оформлению добавляются условия кредитной линии и стоимость доставки, которую рассчитывает учётная система.
Простое разрешение предзаказов в WooCommerce не покрывало этот сценарий. Нужно было показать реальную доступность, получить решение покупателя при нехватке, удержать доступную часть на время корзины и создать документы для двух этапов исполнения без второй оплаты.
Роль и объём
Реализовал правила в теме магазина и связал их с WooCommerce и учётной системой: проверку живого остатка, учёт активных удержаний, выбор количества в корзине, разделение оплаченного заказа, обработку кредитной линии и расчёт доставки. Для удержаний добавил отдельную таблицу и экран проверки в админке. Создание второго заказа вынес в фоновую задачу после оплаты первого.
Ограничения
- Остаток в каталоге может устареть. Для решения о количестве используется свежий ответ учётной системы с учётом активных удержаний магазина.
- Если товара не хватает, первая попытка увеличить количество не должна незаметно превращаться в заказ с отложенной поставкой. Покупатель выбирает доступное количество или полную заявку с ожиданием недостающей части.
- Смешанный заказ оплачивается по итоговой сумме корзины. Второй заказ нужен для исполнения отложенной части, но не должен повторно выставлять покупателю сумму и доставку.
- Кредитная линия живёт по другим правилам: ограниченный счёт или недостаточный лимит переводят весь заказ на согласование и имеют приоритет над выбранной отложенной поставкой.
- Для доставки, зависящей от расчёта учётной системы, отсутствие актуальной котировки — причина остановить оформление, а не подставить старую стоимость.
Архитектура
Корзина покупателя
├── Живой остаток учётной системы
├── Минус активные удержания других корзин
├── Выбор: доступное сейчас / полное количество с ожиданием
└── Удержание отгружаемой части на 10 минут
│
▼
Оформление WooCommerce
├── Повторная сверка доступного количества
├── Снимок суммы корзины и отложенных строк
├── Котировка доставки с проверкой контекста и срока
└── Проверка кредитной линии
│
▼
Заказ
├── Кредитная линия с ограничением → ожидание согласования
└── Оплачиваемая смешанная корзина
├── Первый заказ: отгрузка сейчас + предоплаченная часть
└── После оплаты: фоновая задача создаёт второй заказ
└── Отложенные позиции, ссылка на первый, итог 0
│
▼
Передача заказа в учёт / отмена / возврат
└── Освобождение удержания магазинаПервый заказ хранит платёж и исходный итог корзины. Второй фиксирует отдельный этап исполнения и ссылается на уже оплаченную покупку.
Реализация
Для товаров с живым остатком магазин считает доступное количество как ответ учётной системы минус действующие удержания по тому же товару и окружению клиента. Удержание текущей строки вычитается из общей суммы удержаний, иначе покупатель потерял бы товар, уже удержанный собственной корзиной. В таблицу записывается только количество, которое можно отгрузить сейчас; отложенная часть склад не занимает. Удержание корзины действует десять минут, затем может быть очищено. При создании заказа запись связывается с ним и снимается после успешной передачи заказа в учёт либо при отмене или возврате до передачи.
Если запрошено больше доступного, интерфейс предлагает два варианта. «Только доступное» сокращает количество; выбор полного количества сохраняет на строке отдельно отгружаемую и отложенную части. При последующей проверке чекаута это разделение сверяется с текущим остатком. Ранее принятое решение относится к данной строке корзины, а не автоматически ко всем новым добавлениям товара.
В смешанном заказе строки первого заказа уменьшаются до отгружаемого количества. Стоимость и скидка делятся пропорционально количеству с учётом округления. Отложенные позиции сохраняются снимком, их стоимость добавляется в первый заказ как предоплаченная часть. Итог заказа берётся из корзины до преобразования строк, чтобы сумма оплаты не разошлась с показанной покупателю. После оплаты создание второго заказа ставится в фоновую задачу через Action Scheduler с резервным вариантом через WP-Cron. Второй заказ получает отложенные позиции, адреса и связь с первым, но нулевые доставку и итог. На время его создания подавляются повторные письма об оплате.
Для оплаты по кредитной линии магазин отдельно читает доступный лимит. Если значение получить не удалось, чекаут показывает ошибку. При ограниченном счёте или нехватке лимита весь заказ отправляется на согласование, а обычное разделение на два заказа не выполняется. Явный запрос отложенной поставки по кредитной линии обрабатывается только при достаточном лимите.
Стоимость доставки для нужных методов запрашивается у учётной системы. Удачная котировка хранится в сессии до пяти минут и повторно используется только при совпадении контекста: клиента, окружения, страны, веса и суммы корзины. При изменении параметров WooCommerce пересчитывает тарифы. Если актуальную стоимость получить не удалось, соответствующий тариф убирается, а оформление с ним останавливается. Самовывоз и корзина только с цифровыми лицензиями такого расчёта не требуют; полностью отложенная корзина не требует доставки. Рассчитанная стоимость и её составляющие сохраняются с заказом для просмотра в админке.
Сбои и диагностика
Если остаток не удалось подтвердить, магазин не принимает недоступное количество как гарантированное. При сбое расчёта доставки покупатель видит ошибку и не оформляет заказ по старой цене. Для кредитной линии отсутствие суммы доступного лимита тоже останавливает оформление.
Удержания можно проверить в отдельном экране админки по точному SKU: он показывает живой остаток, активные записи по окружениям, удержанное и доступное количество, владельца корзины или заказ. Связанные заказы хранят идентификаторы друг друга и заметки о создании отложенной части. Эти данные позволяют разбирать расхождения между корзиной, оплатой и последующей передачей в учёт.
границы решения
Правила оформления заказа, удержания товара, разделения поставки и расчёта доставки реализованы в теме магазина. Это связывает бизнес-функциональность с активной темой. Для независимости от смены оформления эти компоненты следует выделить в отдельный плагин, оставив в теме представление корзины и взаимодействие с покупателем.
Удержание действует внутри магазина и не является бронью на складе учётной системы. Расчёт доступного количества и запись удержания выполняются раздельно, поэтому при строго одновременных корзинах код не даёт атомарной гарантии от перепродажи. Для такой гарантии потребуется резервирование на стороне учёта либо транзакционный механизм в магазине.
Второй заказ создаётся фоновой задачей после оплаты. Проверка связи с первым заказом предотвращает обычный последовательный повтор, но создание и запись связи не объединены в одну транзакцию. Если задача завершилась с ошибкой, снимок отложенных строк остаётся в первом заказе для повторной обработки и разбора.
Котировка доставки действует не дольше пяти минут в том контексте, для которого она была получена. Изменение корзины или адреса требует нового расчёта; сам по себе кэш сессии не подтверждает неизменность цены перевозки за пределами этого срока.
Результат
Магазин различает доступную и отложенную части покупки ещё в корзине, а покупатель явно выбирает, как оформить нехватку. Смешанный заказ оплачивается один раз, после чего появляется отдельный документ для отложенного исполнения. Доставка и кредитная линия проверяются по своим правилам до завершения чекаута. Удержания и связи заказов доступны для диагностики, а пределы защиты от одновременных покупок явно определены.
Стек
PHP · WordPress · WooCommerce · REST API · MySQL · Учётная система · Action Scheduler · WP-Cron
Есть похожая задача?
Расскажите о текущей системе и нужных изменениях — обсудим следующий шаг.
Следующий кейс: Выдача оплаченных заказов через дистрибьютора