Клиенты часто просят реализовать акцию «выбери N товаров и получи скидку» — это и есть собери набор. На первый взгляд задача простая, но на практике мы сталкиваемся с подводными камнями: как корректно рассчитать цену при смешанном ценообразовании, как учесть остатки, какой UX не даст пользователю бросить корзину. За 10 лет работы мы внедрили подобный функционал более чем в 50 проектах — от интернет-магазинов косметики до сервисов готовой еды. Типичная ошибка — попытка использовать стандартные правила корзины Битрикс: они не позволяют гибко управлять индивидуальными ценами и группировкой. Мы разработали кастомный механизм на основе инфоблоков и событий корзины, который работает даже при высоких нагрузках (до 1000 одновременных заказов). Свяжитесь с нами, чтобы обсудить ваш проект — мы оценим сложность и предложим оптимальное решение.
Архитектура и UX собери набора
Хранение набора в инфоблоках
Набор описывается элементом инфоблока или записью кастомной таблицы:
// Свойства инфоблока "Наборы" 'MIN_ITEMS' => 3, 'MAX_ITEMS' => 5, 'SET_PRICE' => 1990, 'DISCOUNT_PCT' => 15, 'PRODUCTS' => [12, 15, 18, 22], 'ACTIVE_FROM' => '01 марта', 'ACTIVE_TO' => '01 апреля', При большом пуле товаров (50+) список допустимых позиций задаётся не перечислением ID, а фильтром по разделу или тегу:
'ALLOWED_SECTIONS' => [5, 7, 12], 'ALLOWED_TAGS' => ['promo-may', 'gift-set'], Детали хранения
Каждый набор имеет уникальный код, по которому товары группируются в корзине. Срок действия акции проверяется через агенты, которые автоматически отключают набор по истечении даты.Интерфейс выбора с прогресс-баром
Счётчик прогресса — показывает «Выбрано 2 из 3». Без него пользователь теряется. Реализация:
const maxItems = 3; let selected = []; function toggleProduct(productId, btn) { if (selected.includes(productId)) { selected = selected.filter(id => id !== productId); btn.classList.remove('selected'); } else if (selected.length < maxItems) { selected.push(productId); btn.classList.add('selected'); } updateProgress(); } function updateProgress() { document.querySelector('.progress-text').textContent = `Выбрано ${selected.length} из ${maxItems}`; document.querySelector('.add-to-cart-btn').disabled = selected.length < minItems; } Блокировка лишних выборов — когда выбрано максимальное количество, остальные товары становятся неактивными, но с возможностью снять выбор с уже добавленных. Визуально: карточка серая, кнопка неактивна. Превью набора — справа (на десктопе) или снизу (на мобильном) фиксированная панель со списком выбранных товаров, финальной ценой и кнопкой «В корзину».
Почему важно правильно реализовать ценообразование?
Два режима ценообразования:
Фиксированная цена набора. Пользователь выбрал любые 3 товара — платит $9–13, независимо от цен отдельных позиций. В корзине добавляется специальная позиция «Набор» с ценой 999 р., без детализации по товарам.
Скидка на выбранные позиции. Сумма цен выбранных товаров умножается на коэффициент (например, ×0,85 при скидке 15%). В корзине каждый товар добавляется по сниженной цене.
Для второго варианта в Битрикс можно использовать правила корзины (b_sale_discount), но проще установить цену программно при добавлении в корзину через поле PRICE позиции и CUSTOM_PRICE => 'Y'. Согласно документации Битрикс, такой подход даёт полный контроль над ценообразованием.
| Характеристика | Фиксированная цена | Скидка на позиции |
|---|---|---|
| Пример | $9–13 за набор | 15% на каждую позицию |
| Группировка в корзине | Одна позиция | Каждая позиция со скидкой |
| Сложность реализации | Низкая | Средняя |
| Рекомендуется | Для промо-акций | Для постоянных скидок |
Интеграция с корзиной и заказом
public function addSetToCartAction(int $setId, array $productIds): array { $setData = $this->loadSetData($setId); if (!$this->validateProducts($productIds, $setData)) { return ['success' => false, 'error' => 'Недопустимые товары']; } if (count($productIds) < $setData['MIN_ITEMS'] || count($productIds) > $setData['MAX_ITEMS']) { return ['success' => false, 'error' => 'Неверное количество позиций']; } $prices = $this->calcPrices($productIds, $setData); $setCode = 'bundle_' . $setId . '_' . uniqid(); $basket = \Bitrix\Sale\Basket::loadItemsForFUser(\CSaleBasket::GetBasketUserID(), SITE_ID); foreach ($productIds as $i => $pid) { $item = $basket->createItem('catalog', $pid); $item->setFields([ 'QUANTITY' => 1, 'CUSTOM_PRICE' => 'Y', 'PRICE' => $prices[$i], 'BASE_PRICE' => $prices[$i], ]); $propCol = $item->getPropertyCollection(); $propCol->setProperty(['CODE' => 'SET_CODE', 'VALUE' => $setCode]); } $basket->save(); return ['success' => true, 'basket_count' => count($basket)]; } В корзине товары из одного набора отображаются как группа. Шаблон bitrix:sale.basket.basket кастомизируется: товары группируются по SET_CODE, отображаются со скидкой и с возможностью редактировать набор (ссылка обратно на страницу конструктора). В заказе сохраняется SET_CODE как реквизит позиции — для корректной обработки возвратов и аналитики.
Как внедрить функционал за 5 этапов?
- Анализ требований — определение типа ценообразования, пула товаров, сроков акции.
- Проектирование — создание инфоблоков, настройка свойств, разработка схемы данных.
- Реализация — кодирование интерфейса, логики цен, интеграция с корзиной.
- Тестирование — проверка всех сценариев: выбор, снятие, остатки, одновременные акции.
- Деплой и документирование — выкатка на продакшн, обучение операторов.
Проверка остатков и кеширование
Если товар из набора закончился на складе, его нельзя выбирать. Проверка остатков — через CCatalogProduct::GetByID() или через \Bitrix\Catalog\ProductTable:
$product = \Bitrix\Catalog\ProductTable::getRow([ 'filter' => ['ID' => $productId], 'select' => ['QUANTITY', 'QUANTITY_TRACE', 'CAN_BUY_ZERO'], ]); $available = ($product['CAN_BUY_ZERO'] === 'Y') || ($product['QUANTITY'] > 0); Остатки кешируются на 5–10 минут — слишком частые запросы к базе при большом пуле товаров нагружают систему. Оптимизация: используйте тегированное кеширование и обновляйте кеш при обновлении остатков через агент.
Аналитика конверсии
Набор — это конверсионный инструмент, его эффективность нужно измерять. Что отслеживать:
- Сколько пользователей начали выбор (просмотр страницы набора)
- Сколько завершили выбор и добавили в корзину
- Сколько дошли до оплаты
- Средний состав набора (какие товары выбираются чаще)
События передаются в Яндекс.Метрику или Google Analytics через dataLayer.push() при каждом шаге.
Сроки разработки
| Вариант | Что входит | Срок | Экономия при тиражировании |
|---|---|---|---|
| Простой набор (фикс. цена) | UI выбора + корзина + страница набора | 1–2 недели | до 30% |
| Со скидкой на позиции + ограничения | Расчёт скидок, остатки, аналитика | 2–4 недели | до 40% |
| Несколько активных наборов | Управление в админке, сроки акций | 3–5 недель | до 50% |
Стоимость разработки рассчитывается индивидуально в зависимости от сложности и количества интеграций.
Функционал «собери набор» работает лучше всего как ограниченная акция. Срок действия создаёт urgency, а возможность выбора — ощущение персонализации. Это сочетание конвертирует лучше, чем статичный готовый набор по такой же цене. Получите консультацию инженера с 10-летним опытом в Битрикс — просто свяжитесь с нами.







