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.







