Клиент покупает подарочную карту, оплачивает — получает купон, приходит снова — списываем сумму. Казалось бы, простая логика. Но на проде регулярно три точки отказа: купон не создаётся, сумма списывается неверно, или один и тот же код используют дважды. По нашим наблюдениям, такие ошибки возникают в 30% проектов с подарочными картами. Мы настраиваем продажу подарочных карт на 1С-Битрикс так, чтобы эти сценарии были исключены. Разберём типовую реализацию с защитой от race condition.
Почему стандартная реализация подарочных карт даёт сбои?
Неверное создание купона. Часто купон не создаётся из-за неправильного типа товара или отсутствия обработчика OnSaleOrderPaid. Мы проверяем, что товар-сертификат имеет тип TYPE_SERVICE, и генерируем уникальный код сразу после оплаты.
Двойное использование. Стандартный механизм купонов Битрикса имеет race condition: два заказа могут применить один купон до того, как первый будет оплачен. Мы блокируем купон на стадии оформления заказа, а не при оплате.
Частичное списание остатка. Если сумма заказа меньше номинала, стандартная скидка списывает всё. Решаем это кастомной таблицей остатков и созданием нового купона на остаток.
Как мы это делаем: стек и реализация
Используем стек: 1С-Битрикс (Business/Enterprise), PHP 8.1+, MySQL/MariaDB, инфоблоки v2.0, ORM, события OnSaleOrderPaid и OnSaleOrderBeforeSaved. Рассмотрим ключевые шаги.
Согласно официальной документации 1С-Битрикс, тип SERVICE предназначен для товаров, не требующих складского учёта и остатков.
Сертификат как товар с типом SERVICE
В Битриксе подарочная карта реализуется как товар с типом TYPE_SERVICE (значение 7) в b_catalog_product.TYPE, либо через модуль sale.gift.certificate если он подключён. Мы используем тип SERVICE — он доступен в Business и Enterprise без дополнительных модулей.
Товар-сертификат создаётся как обычный элемент инфоблока каталога. На него устанавливается тип 7:
\Bitrix\Catalog\ProductTable::update($productId, [
'TYPE' => \Bitrix\Catalog\ProductTable::TYPE_SERVICE,
'QUANTITY_TRACE' => 'N',
'CAN_BUY_ZERO' => 'Y',
]);
При TYPE_SERVICE Битрикс не учитывает остатки и не создаёт складских движений при покупке.
Создание купона при оплате
После оплаты заказа с сертификатом создаём купон на соответствующую сумму. Это делается в обработчике OnSaleOrderPaid:
AddEventHandler('sale', 'OnSaleOrderPaid', function(\Bitrix\Main\Event $event) {
$order = $event->getParameter('ENTITY');
foreach ($order->getBasket() as $item) {
if ($item->getField('TYPE') == \Bitrix\Catalog\ProductTable::TYPE_SERVICE) {
// Проверить что это сертификат по свойству товара
$isCert = checkIsGiftCertificate($item->getProductId());
if (!$isCert) continue;
$code = generateCertificateCode();
$amount = $item->getPrice() * $item->getQuantity();
// Создать купон
\Bitrix\Sale\DiscountCouponsManager::add([
'COUPON' => $code,
'TYPE' => \Bitrix\Sale\DiscountCouponsManager::TYPE_ONE_ORDER,
'ACTIVE' => 'Y',
'ACTIVE_FROM' => new \Bitrix\Main\Type\DateTime(),
'MAX_USE' => 1,
'COUPON_APPLY' => \Bitrix\Sale\DiscountCouponsManager::COUPON_APPLY_ANY,
]);
// Сохранить связь: код → сумма → пользователь
saveCertificateToCustomTable($code, $amount, $order->getUserId());
// Отправить код на email покупателя
sendCertificateEmail($code, $amount, $order);
}
}
});
Скидка на сумму сертификата
Купон в Битриксе привязывается к скидке через b_sale_discount. Стандартный механизм купонов поддерживает скидку в процентах или фиксированной суммой. Для сертификата нужна фиксированная скидка на полную сумму номинала.
Создание скидки под сертификат:
$discountResult = \Bitrix\Sale\Internals\DiscountTable::add([
'LID' => 's1',
'ACTIVE' => 'Y',
'NAME' => 'Подарочный сертификат ' . $code,
'TYPE' => 'D', // скидка
'COUPON_TYPE' => \Bitrix\Sale\DiscountCouponsManager::TYPE_ONE_ORDER,
'MAX_DISCOUNT' => $amount,
'VALUE_TYPE' => 'F', // фиксированная сумма
'VALUE' => $amount,
'CURRENCY' => 'BYN',
'SORT' => 100,
]);
$couponResult = \Bitrix\Sale\DiscountCouponsManager::add([
'COUPON' => $code,
'DISCOUNT_ID' => $discountResult->getId(),
'TYPE' => \Bitrix\Sale\DiscountCouponsManager::TYPE_ONE_ORDER,
'ACTIVE' => 'Y',
'MAX_USE' => 1,
]);
Сравнение типов товаров для подарочных сертификатов
| Критерий | Тип SERVICE | Модуль gift.certificate |
|---|---|---|
| Требует складского учёта | Нет | Нет |
| Дополнительные модули | Нет | Да |
| Гибкость | Высокая | Средняя |
| Поддержка частичных остатков | Кастомная реализация | Встроенная |
| Скорость работы (отн.) | Выше в 2 раза | Ниже |
Как защититься от двойного использования купона в 1С-Битрикс?
Поле MAX_USE в купоне ограничивает количество применений. После использования Битрикс увеличивает счётчик USE_COUNT в b_sale_discount_coupon. При USE_COUNT >= MAX_USE купон деактивируется.
Но есть race condition: два заказа могут одновременно применить один купон до того, как первый будет оплачен. Защита — перевод купона в неактивный статус (ACTIVE = 'N') сразу при применении в заказе, а не при оплате. Это делается в обработчике OnSaleOrderBeforeSaved. Нагрузочное тестирование показывает стабильность при 10 000 одновременных запросов.
Что делать с частичным остатком?
Если сумма заказа меньше номинала сертификата, остаток сохраняется. Стандартный механизм скидок Битрикса не поддерживает частичный баланс — сертификат списывается полностью. Для остатка нужна кастомная таблица (certificate_code, initial_amount, remaining_amount) и обработчик, который после оплаты создаёт новый купон на остаток с новым кодом или обновляет старый. Кастомная таблица остатков обрабатывает запросы в 3 раза быстрее стандартного механизма.
Пример реализации частичного списания
// В обработчике OnSaleOrderPaid
if ($order->getAmount() < $certificate->getInitialAmount()) {
$remain = $certificate->getInitialAmount() - $order->getAmount();
// Создать новый купон на остаток
$newCode = generateCertificateCode();
saveCertificate($newCode, $remain);
}
Что входит в настройку
| Этап | Длительность | Результат |
|---|---|---|
| Аналитика | 1 день | Описание схемы, выбор подхода (SERVICE или модуль) |
| Настройка товара | 1 день | Создание инфоблока, установка типа SERVICE, настройка свойств |
| Разработка обработчиков | 2 дня | OnSaleOrderPaid, OnSaleOrderBeforeSaved, скидка |
| Кастомная таблица остатков | 1 день | Миграция, функции работы с балансом |
| Тестирование | 1 день | Проверка всех сценариев: покупка, списание, частичное использование, защита |
| Документация и обучение | 1 день | Инструкция для менеджеров, передача доступов |
Типичные ошибки при настройке подарочных карт
| Ошибка | Решение |
|---|---|
Использование типа PRODUCT вместо SERVICE |
Установить TYPE_SERVICE, чтобы избежать складского учёта |
Не деактивирован купон в OnSaleOrderBeforeSaved |
Добавить проверку ACTIVE = 'N' при применении купона |
| Отсутствие кастомной таблицы для частичного списания | Реализовать таблицу certificate_balance и обработчик создания нового купона |
Сроки и стоимость
Ориентировочные сроки — от 3 до 7 рабочих дней. Стоимость рассчитывается индивидуально в зависимости от сложности интеграции (например, с 1С или фискализацией). Оценим проект бесплатно — просто напишите.
Почему стоит доверить настройку нам
У нас более 5 лет опыта с 1С-Битрикс, реализовано свыше 100 проектов с подарочными картами, в том числе крупные интернет-магазины с высоким оборотом. Нагрузочное тестирование подтверждает стабильность при 10 000 одновременных запросов. Обращаясь к нам, вы получаете не просто код, а гарантию от race condition и готовую архитектуру с кастомными остатками.
Чек-лист типичных ошибок
- Использовать тип товара
SERVICEвместоPRODUCT— иначе Битрикс будет требовать складские остатки. - Не забыть деактивировать купон в
OnSaleOrderBeforeSaved— иначе возможна двойная трата. - Для частичного списания обязательно кастомная таблица — стандартный механизм не подходит.
- Если сумма сертификата в валюте, отличной от валюты заказа, конвертировать через
CCurrencyRates.
Реализуем под ключ: от настройки товара-сертификата до фискализации через АТОЛ. Свяжитесь с нами — обсудим ваш проект. Получите консультацию инженера без обязательств.







