Configuring Unified Loyalty Program Online and Offline 1С-Bitrix
Customer accumulated 500 bonus points on the site and came to the store to spend them. Cashier doesn't see the bonuses — they're in a different system. Or vice versa: purchase in the store doesn't credit online account. Gap between online and offline loyalty program is not only a UX problem, but also direct losses on repeat purchases.
How loyalty works in Bitrix
The sale module implements discount system via b_sale_user_discount (personal discounts) and bonus system via b_sale_discount (cart rules). For full-featured loyalty program (accumulative points), a separate module or external system integration is used.
Bitrix24 has CRM module with bonuses (b_crm_loyalty_bonus_transaction), but for online store without Bitrix24, the marketingcrm module or custom transactions table is more often applied.
Bonus storage structure
Minimal structure for unified program:
CREATE TABLE bl_loyalty_account (
id SERIAL PRIMARY KEY,
user_id INT UNIQUE, -- b_user.ID (online)
card_number VARCHAR(20) UNIQUE, -- card number for offline
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, -- positive=earn, negative=spend
type VARCHAR(20) NOT NULL, -- 'earn_online', 'earn_offline', 'spend', 'expire'
order_id INT, -- b_sale_order.ID or external offline receipt ID
source VARCHAR(20) NOT NULL, -- 'web', 'pos', 'mobile'
created_at TIMESTAMP DEFAULT NOW()
);
Transactional model with history is the only reliable way to store bonuses. Never update balance directly without recording a transaction. balance is either denormalized aggregate (updated by DB trigger) or calculated as SUM(amount) from the transactions table.
Customer identification at offline cashbox
Key task: cashier must find customer account. Identification methods:
- Loyalty card number (physical card or barcode in mobile app)
- Phone number
- QR-code with token (generated in personal account)
When identifying by phone, cashbox software sends request to Bitrix 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']]);
}
Bonus accrual on online order
Event handler on status change — after delivery/completion:
AddEventHandler('sale', 'OnSaleStatusOrderChange', function(\Bitrix\Main\Event $event) {
$order = $event->getParameter('ENTITY');
$status = $order->getField('STATUS_ID');
if ($status !== 'F') return; // Only completed orders
$userId = $order->getUserId();
$bonus = round($order->getPrice() * BONUS_RATE); // BONUS_RATE = 0.05 (5%)
addLoyaltyTransaction($userId, $bonus, 'earn_online', $order->getId(), 'web');
});
Bonus deduction on online purchase
Bonuses are applied via cart rule or custom payment method. Cart rule (b_sale_discount) can give fixed-amount discount. For more flexible scheme — custom "payment method" like "Pay with Bonuses", which when completing the order deducts transaction from bl_loyalty_transaction.
Synchronization via API for offline cashbox
Offline cashbox calls three endpoints:
-
GET /loyalty/balance?phone=...— check balance -
POST /loyalty/spend— deduct bonuses on sale (must be transactional: sale start → reservation → confirmation) -
POST /loyalty/earn— accrue bonuses after sale
Reservation on deduction is a critical step. Without it, two parallel requests from different cashboxes can simultaneously read balance of 500 bonuses and deduct 500 twice, going into negative. Reservation via SELECT ... FOR UPDATE in PostgreSQL or via strict UPDATE with result check:
UPDATE bl_loyalty_account
SET balance = balance - :amount
WHERE id = :account_id AND balance >= :amount
RETURNING balance;
-- If 0 rows updated — insufficient bonuses
What we configure
- Tables
bl_loyalty_accountandbl_loyalty_transaction - API-endpoints for cashbox systems (balance, accrual, deduction)
- Event handlers
OnSaleStatusOrderChangefor online accruals - Customer identification mechanism by phone/card
- Transactional deduction with race condition protection
- Administrative interface for transaction history viewing







