Development of a loyalty program module for 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1177
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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.