Setting up cashback accrual rules by 1C-Bitrix categories

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
    1175
  • 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

Configuring Category-Based Cashback Accrual Rules in 1C-Bitrix

A marketer wants to give 5% cashback on electronics, 2% on household chemicals, and 0% on sale items. Rules must also be combinable: a "Gold" loyalty card adds +1% on top of the base rate in any category. The standard discount module (catalog.discount) is not suitable — it operates on price reductions, not account accruals. A separate rules system is required.

Rules Architecture

Rules are stored in the local_cashback_rules table. Minimum structure:

CREATE TABLE local_cashback_rules (
    ID           INT AUTO_INCREMENT PRIMARY KEY,
    RULE_TYPE    ENUM('category','product','user_group','promo') NOT NULL,
    ENTITY_ID    INT,           -- infoblock section ID, product ID, or group ID
    CASHBACK_PCT DECIMAL(5,2),  -- accrual percentage
    PRIORITY     INT DEFAULT 10,-- lower value = higher priority
    DATE_FROM    DATE,
    DATE_TO      DATE,
    ACTIVE       CHAR(1) DEFAULT 'Y'
);

Rules with RULE_TYPE = 'category' are tied to catalog infoblock sections. When accruing cashback for an order, it is necessary to determine which section each product belongs to and find the applicable rule.

Determining a Product's Category

A product may belong to multiple sections (multiple assignment). For rule matching, the "main" section is used:

function getCashbackRateForProduct(int $productId): float
{
    // Main section via b_iblock_element
    $element = \CIBlockElement::GetByID($productId)->Fetch();
    $sectionId = (int)$element['IBLOCK_SECTION_ID'];

    // Look for a rule: first by exact section, then by parents
    while ($sectionId > 0) {
        $rule = CashbackRuleTable::getActiveRuleForSection($sectionId);
        if ($rule) {
            return (float)$rule['CASHBACK_PCT'];
        }

        // Traverse up the section tree
        $section = \CIBlockSection::GetByID($sectionId)->Fetch();
        $sectionId = (int)$section['IBLOCK_SECTION_ID'];
    }

    // Default rule
    return CashbackConfig::getDefaultRate();
}

Rule inheritance through the section tree: if "Electronics" is set to 5% and "Laptops" has no explicit rule, laptops inherit 5% from the parent section. An explicit rule on a child section always overrides the parent.

Priority and Rule Combination

Complex logic with multiple simultaneously active rules — via priorities:

function resolveCashbackRate(int $productId, int $userId): float
{
    $baseRate = getCashbackRateForProduct($productId);

    // Additional rules by user group
    $userGroups = CUser::GetUserGroup($userId);
    $bonusRates = [];

    foreach ($userGroups as $groupId) {
        $rule = CashbackRuleTable::getQuery()
            ->setFilter([
                'RULE_TYPE'  => 'user_group',
                'ENTITY_ID'  => $groupId,
                'ACTIVE'     => 'Y',
                '<=DATE_FROM' => new \Bitrix\Main\Type\Date(),
                '>=DATE_TO'   => new \Bitrix\Main\Type\Date(),
            ])
            ->setOrder(['PRIORITY' => 'ASC'])
            ->fetchObject();

        if ($rule) {
            $bonusRates[] = (float)$rule->getCashbackPct();
        }
    }

    // Strategy: take the maximum group bonus + base category rate
    $bonusRate = empty($bonusRates) ? 0 : max($bonusRates);

    return $baseRate + $bonusRate;
}

The addition or replacement strategy is a business decision. For most loyalty programs: category rate + group bonus (addition), but not exceeding the maximum allowed percentage.

Exclusions: Promotional Products and Promo Periods

A zero rate on promotional products is implemented as a rule with CASHBACK_PCT = 0 and the highest priority (lowest numeric value in the PRIORITY field). A product is considered promotional if it has a discount applied through the catalog.discount mechanism or via the custom property IS_PROMO = Y.

Promo periods — the same rules with DATE_FROM and DATE_TO. Automatic activation/deactivation without developer intervention.

Accrual on Order Completion

Cashback is accrued when the order status changes to "Fulfilled" (not on payment — to avoid accruing on returned items):

AddEventHandler('sale', 'OnSaleStatusOrder', function(string $statusId, \Bitrix\Sale\Order $order) {
    if ($statusId !== 'F') { // F = Fulfilled
        return;
    }

    $userId = $order->getUserId();
    $totalCashback = 0;

    foreach ($order->getBasket() as $item) {
        $productId = (int)$item->getProductId();
        $rate      = resolveCashbackRate($productId, $userId);
        $cashback  = $item->getPrice() * $item->getQuantity() * ($rate / 100);
        $totalCashback += $cashback;

        // Record per line item for detailed history
        CashbackTransactionTable::add([
            'USER_ID'    => $userId,
            'ORDER_ID'   => $order->getId(),
            'PRODUCT_ID' => $productId,
            'AMOUNT'     => $cashback,
            'RATE'       => $rate,
            'TYPE'       => 'accrual',
        ]);
    }

    CashbackBalanceTable::credit($userId, $totalCashback);
});

Managing Rules from the Admin Panel

The rules management interface is built with CAdminList + CAdminForm or a React component in the /local/admin/ section. Minimum feature set: rules list with filter by type/active status, edit form with the catalog section tree for category selection.

Timeline

Task Duration
Rules tables, basic category logic 3–5 days
Group bonuses, exclusions, promo periods 3–5 days
Accrual on order with detailed history 2–3 days
Rules management interface in admin panel 3–5 days
Full package 2–3 weeks