Настройка единой программы лояльности онлайн и оффлайн 1С-Битрикс
Клиент накопил 500 бонусов на сайте и пришёл в магазин их потратить. Кассир не видит бонусов — они в другой системе. Или наоборот: покупка в магазине не начисляет баллы на онлайн-аккаунт. Разрыв между онлайн и оффлайн программой лояльности — это не только UX-проблема, но и прямые потери на повторных покупках.
Как устроена лояльность в Битрикс
Модуль sale реализует дисконтную систему через b_sale_user_discount (персональные скидки) и бонусную систему через b_sale_discount (правила корзины). Для полноценной программы лояльности (накопительные баллы) используется отдельный модуль или интеграция с внешней системой.
В Битрикс24 есть CRM-модуль с бонусами (b_crm_loyalty_bonus_transaction), но для интернет-магазина без Битрикс24 чаще применяется либо модуль marketingcrm, либо собственная таблица транзакций.
Структура хранения бонусов
Минимальная структура для единой программы:
CREATE TABLE bl_loyalty_account (
id SERIAL PRIMARY KEY,
user_id INT UNIQUE, -- b_user.ID (онлайн)
card_number VARCHAR(20) UNIQUE, -- номер карты для оффлайн
balance NUMERIC(10,2) DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE bl_loyalty_transaction (
id SERIAL PRIMARY KEY,
account_id INT NOT NULL REFERENCES bl_loyalty_account(id),
amount NUMERIC(10,2) NOT NULL, -- положительное=начисление, отрицательное=списание
type VARCHAR(20) NOT NULL, -- 'earn_online', 'earn_offline', 'spend', 'expire'
order_id INT, -- b_sale_order.ID или внешний ID оффлайн-чека
source VARCHAR(20) NOT NULL, -- 'web', 'pos', 'mobile'
created_at TIMESTAMP DEFAULT NOW()
);
Транзакционная модель с историей — единственный надёжный способ хранить бонусы. Никогда не обновляйте balance напрямую без записи транзакции. balance — это либо денормализованный агрегат (обновляется триггером БД), либо считается как SUM(amount) по таблице транзакций.
Идентификация клиента на оффлайн-кассе
Ключевая задача: кассир должен найти аккаунт клиента. Способы идентификации:
- Номер карты лояльности (физическая карта или штрихкод в мобильном приложении)
- Номер телефона
- QR-код с токеном (генерируется в личном кабинете сайта)
При идентификации по телефону кассовое ПО отправляет запрос к API Битрикс:
// /local/ajax/loyalty/find-account.php
$phone = normalizePhone($_POST['phone']);
$bitrixUser = \CUser::GetList([], ['PERSONAL_PHONE' => $phone])->Fetch();
if ($bitrixUser) {
$account = getLoyaltyAccount($bitrixUser['ID']);
echo json_encode(['balance' => $account['balance'], 'account_id' => $account['id']]);
}
Начисление бонусов при онлайн-заказе
Обработчик смены статуса заказа — после доставки/завершения:
AddEventHandler('sale', 'OnSaleStatusOrderChange', function(\Bitrix\Main\Event $event) {
$order = $event->getParameter('ENTITY');
$status = $order->getField('STATUS_ID');
if ($status !== 'F') return; // Только завершённые заказы
$userId = $order->getUserId();
$bonus = round($order->getPrice() * BONUS_RATE); // BONUS_RATE = 0.05 (5%)
addLoyaltyTransaction($userId, $bonus, 'earn_online', $order->getId(), 'web');
});
Списание бонусов при онлайн-оплате
Бонусы применяются через правило корзины или через кастомный платёжный метод. Правило корзины (b_sale_discount) может давать скидку фиксированной суммой. Для более гибкой схемы — собственный «платёжный метод» типа «Оплата бонусами», который при проведении заказа списывает транзакцию из bl_loyalty_transaction.
Синхронизация через API для оффлайн-кассы
Оффлайн-касса вызывает три endpoint:
-
GET /loyalty/balance?phone=...— проверить баланс -
POST /loyalty/spend— списать бонусы при продаже (должен быть транзакционным: начало продажи → резервирование → подтверждение) -
POST /loyalty/earn— начислить бонусы после продажи
Резервирование при списании — критически важный шаг. Без него два параллельных запроса от разных касс могут одновременно прочитать баланс 500 бонусов и дважды списать по 500, уйдя в минус. Резервирование через SELECT ... FOR UPDATE в PostgreSQL или через строгое UPDATE с проверкой результата:
UPDATE bl_loyalty_account
SET balance = balance - :amount
WHERE id = :account_id AND balance >= :amount
RETURNING balance;
-- Если обновлено 0 строк — недостаточно бонусов
Что настраиваем
- Таблицы
bl_loyalty_accountиbl_loyalty_transaction - API-endpoints для кассовых систем (баланс, начисление, списание)
- Обработчики событий
OnSaleStatusOrderChangeдля онлайн-начислений - Механизм идентификации клиента по телефону/карте
- Транзакционное списание с защитой от race condition
- Административный интерфейс для просмотра истории транзакций







