1C-Bitrix Loyalty Programme Module Development
The built-in "bonuses" tool in Bitrix is essentially the field b_user.UF_BONUS_POINTS plus hand-crafted logic in components. There is no accrual history, no point expiry, no tiers, no integration with orders via events. When a client says "we want a loyalty programme", they mean something far more substantial: a cumulative system with tiers, history, limited point lifetimes, partial order payment, and a privilege showcase.
Data Model
The vendor.loyalty module:
-
b_vendor_loyalty_account— loyalty account: id, user_id, balance (current balance), total_earned (all-time total earned), level_id, created_at -
b_vendor_loyalty_transaction— transactions: id, account_id, type (earn/spend/expire/cancel/manual), amount, order_id, description, expires_at, created_at -
b_vendor_loyalty_level— programme tiers: id, name, min_points, earn_multiplier, privileges (JSON), icon_id -
b_vendor_loyalty_rule— accrual rules: id, event_type (order_paid/review_added/birthday/referral/registration), points_type (fixed/percent), points_value, conditions (JSON), is_active
Point Accrual
Accrual is tied to Bitrix events. For orders — OnSaleOrderPaid:
AddEventHandler('sale', 'OnSaleOrderPaid', ['\Vendor\Loyalty\EventHandler', 'onOrderPaid']);
public static function onOrderPaid(\Bitrix\Main\Event $event): void
{
$orderId = $event->getParameter('id');
$order = \Bitrix\Sale\Order::load($orderId);
$userId = $order->getUserId();
$account = AccountTable::getByUserId($userId);
$level = LevelTable::getById($account['LEVEL_ID']);
// Accrue % of order total with the tier multiplier applied
$basePoints = floor($order->getPrice() * 0.05); // 5% base rate
$earnedPoints = floor($basePoints * $level['EARN_MULTIPLIER']);
TransactionTable::add([
'ACCOUNT_ID' => $account['ID'],
'TYPE' => 'earn',
'AMOUNT' => $earnedPoints,
'ORDER_ID' => $orderId,
'EXPIRES_AT' => (new DateTime())->add(new DateInterval('P1Y')), // 1-year lifetime
'DESCRIPTION' => "Points earned for order #{$order->getField('ACCOUNT_NUMBER')}",
]);
AccountTable::update($account['ID'], [
'BALANCE' => $account['BALANCE'] + $earnedPoints,
'TOTAL_EARNED' => $account['TOTAL_EARNED'] + $earnedPoints,
]);
// Check for tier upgrade
LevelUpgradeService::check($account['ID']);
}
Point Expiry
Points can have an expiry date. A daily agent checks and expires overdue points:
// Agent runs at 03:00 AM
$expired = TransactionTable::getList([
'filter' => ['TYPE' => 'earn', '<=EXPIRES_AT' => new DateTime(), '>AMOUNT' => 0],
])->fetchAll();
foreach ($expired as $tx) {
// Deduct remaining points from this transaction
TransactionTable::add(['TYPE' => 'expire', 'AMOUNT' => -$tx['REMAINING'], ...]);
// Update account balance
}
Spending Points at Checkout
Partial order payment with points is implemented via a custom payment system (PaySystem):
// In the payment system processor
public function processRequest(Payment $payment, Request $request): ProcessRequestResult
{
$pointsToSpend = (int)$request->get('loyalty_points');
$account = AccountTable::getByUserId($payment->getOrder()->getUserId());
if ($account['BALANCE'] < $pointsToSpend) {
return ProcessRequestResult::error('Insufficient points');
}
// 1 point = 1 currency unit (configurable in the module)
$discountAmount = $pointsToSpend / (int)Option::get('vendor.loyalty', 'points_rate', 1);
$discountAmount = min($discountAmount, $payment->getSum() * 0.5); // maximum 50% of order
// Deduct points and apply discount to the order
TransactionTable::add(['TYPE' => 'spend', 'AMOUNT' => -$pointsToSpend, ...]);
AccountTable::update($account['ID'], ['BALANCE' => $account['BALANCE'] - $pointsToSpend]);
return ProcessRequestResult::success();
}
Programme Tiers
Tiers are upgraded automatically based on TOTAL_EARNED. Upon tier upgrade:
- The accrual multiplier (
EARN_MULTIPLIER) is recalculated - A notification is sent to the user
- Tier privileges are activated (free shipping, priority support — via flags in
b_user.UF_*)
| Tier | Threshold (total points) | Accrual multiplier |
|---|---|---|
| Standard | 0 | 1.0x |
| Silver | 5,000 | 1.2x |
| Gold | 20,000 | 1.5x |
| Platinum | 50,000 | 2.0x |
Thresholds and multipliers are configurable in the administrative interface.
User Personal Account
The "My Loyalty" block in the personal account:
- Current balance and tier
- Progress to the next tier
- Transaction history with pagination
- Nearest expiring points with dates
- Points application form at checkout
Development Timeline
| Stage | Duration |
|---|---|
| ORM tables, account and transaction model | 1 day |
| Accrual rules, event handlers | 2 days |
| Point expiry, expiry agent | 1 day |
| PaySystem for point spending | 2 days |
| Tiers, auto-upgrade, privileges | 2 days |
| Personal account, transaction history | 2 days |
| Administrative interface | 1 day |
| Testing | 1 day |
Total: 12 working days. Integration with an offline POS or CRM for accruing points on off-site purchases — separate estimate required.







