Setting up personal discounts for 1C-Bitrix client groups

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
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • 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
    565
  • 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
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    980

Configuring Personal Discounts by Customer Groups in 1C-Bitrix

A manager creates "15% discount for VIP customers" in Bitrix admin and assigns it to a user group. A test VIP account logs in — but the discount doesn't apply. The reason: the discount was created in "Catalog," but the user doesn't belong to the required group because the manager confused "buyer group" (sale module) with "user group" (main module). In Bitrix, these are different entities, and confusion between them is the source of most personal discount issues.

Discount Architecture in Bitrix

The sale module manages discounts via \Bitrix\Sale\Discount. Discounts can be applied to:

  • User groups (b_user_group — main module table)
  • Individual users (via advanced conditions)
  • Buyers with specific order history (accumulative discounts)

To assign a discount to a group: in the admin panel, go to "Shop → Price Rules" — select "Product Discount" or "Order Discount," in conditions specify "Buyer belongs to group." Groups are taken from b_user_group.

Creating a Group and Binding Users

User groups are created in "Settings → User Groups." Programmatically:

$group = new \CGroup();
$groupId = $group->Add([
    'NAME'          => 'VIP Customers',
    'DESCRIPTION'   => '15% discount on entire catalog',
    'ACTIVE'        => 'Y',
    'SORT'          => 100,
]);

Add a user to a group:

$user = new \CUser();
$user->Update($userId, ['GROUP_ID' => array_merge($currentGroups, [$groupId])]);

Or use CUser::SetUserGroup($userId, $groups) — it overwrites all groups, so you must pass current groups along with the new one.

Automatic Group Assignment by Conditions

To automatically move a buyer to a VIP group when reaching an order amount threshold, use the OnSaleOrderSaved event handler:

AddEventHandler('sale', 'OnSaleOrderSaved', function(\Bitrix\Main\Event $event) {
    $order = $event->getParameter('ENTITY');
    $userId = (int)$order->getUserId();
    if (!$userId) return;

    // Calculate sum of paid orders
    $total = 0;
    $res = \Bitrix\Sale\Order::getList([
        'filter' => ['USER_ID' => $userId, 'PAYED' => 'Y'],
        'select' => ['PRICE'],
    ]);
    while ($row = $res->fetch()) {
        $total += $row['PRICE'];
    }

    $vipThreshold = 50000; // currency units
    $vipGroupId   = 5;

    $dbUser = \CUser::GetByID($userId)->Fetch();
    $currentGroups = array_map('intval', explode(',', $dbUser['GROUP_IDS'] ?? ''));

    if ($total >= $vipThreshold && !in_array($vipGroupId, $currentGroups)) {
        $currentGroups[] = $vipGroupId;
        (new \CUser())->Update($userId, ['GROUP_ID' => $currentGroups]);
    }
});

Discounts on Trade Offers (SKU)

If a discount is assigned to a parent product, it applies to trade offers too. But if you need a discount only on a specific SKU — the condition in the price rule must reference the offers infoblock. In rule settings: "Infoblock Type" → select the offers infoblock, section, or specific elements.

Displaying Personal Price

The standard catalog.element component displays price via $arResult['CATALOG_PRICE_*'], where * is the price type. Discounts for groups are applied via \CCatalogProduct::GetOptimalPrice(), which considers price rules and the current user's groups. If the page is cached — the price in cache may be without discount.

Solution: move the price block to a separate non-cached component or fetch the actual price via AJAX after page load. The second approach is better for performance under high traffic.