Прокачиваем бонусную систему в 1С-Битрикс: лимиты, сгорание баллов и правильная выгрузка в 1С:УНФ

Бонусная система в 1С-Битрикс

Стандартные модули лояльности для 1С-Битрикс часто решают лишь базовые задачи — начислить баллы и списать их. Но когда бизнес начинает считать деньги, появляются более сложные требования: нельзя оплачивать баллами доставку, нельзя списывать бонусы на акционные товары, а старые баллы должны «сгорать», чтобы не висеть мертвым грузом в базе.

И самое главное — бухгалтерия и менеджеры в 1С:УНФ должны четко видеть, сколько реальных денег должен заплатить клиент.

В этой статье мы шаг за шагом кастомизируем модуль лояльности (на примере itsector.bonus) и настроим правильный обмен с 1С.

Задача 1. Жесткие лимиты на списание баллов в корзине

Что нужно сделать:

  1. Ограничить оплату баллами до 30% от стоимости заказа.

  2. Запретить оплачивать баллами стоимость доставки.

  3. Исключить из расчета лимита товары, на которые уже действует скидка (акционные).

Бэкенд-защита (Ядро компонента)

Переопределяем логику в файле class.php компонента sale.order.ajax. Находим метод, отвечающий за внутренний счет (getInnerPaySystemInfo), и внедряем наш расчет лимитов:

// --- НАЧАЛО: ЖЕСТКОЕ ОГРАНИЧЕНИЕ СПИСАНИЯ БОНУСОВ ---
if (\Bitrix\Main\Loader::includeModule('itsector.bonus')) {
$bonusPercent = (float)\Bitrix\Main\Config\Option::get('itsector.bonus', 'max_deduct_percent', 30);

// Считаем базу для бонусов: только товары БЕЗ скидок
$basketPriceForBonus = 0;
foreach ($order->getBasket() as $basketItem) {
// Если скидка на товар равна 0 (он не акционный)
if ($basketItem->getDiscountPrice() <= 0) {
$basketPriceForBonus += $basketItem->getFinalPrice();
}
}

$deliveryPrice = $order->getDeliveryPrice();
$orderPrice = $order->getPrice();

// 1. Лимит 30% только от товаров без скидки
$maxAllowedByPercent = floor($basketPriceForBonus * ($bonusPercent / 100));

// 2. Лимит "без доставки"
$maxWithoutDelivery = $orderPrice - $deliveryPrice;

// Выбираем самое жесткое ограничение
$finalMaxSum = max(0, min($maxAllowedByPercent, $maxWithoutDelivery));

// Если покупатель пытается списать больше лимита — принудительно режем
if ($sumToSpend > $finalMaxSum) {
$sumToSpend = $finalMaxSum;
}
}
// --- КОНЕЦ: ЖЕСТКОЕ ОГРАНИЧЕНИЕ ---

Передача лимита во фронтенд

В том же class.php обновляем метод getBonusSettings, чтобы передать правильную максимальную сумму в браузер пользователя:

protected function getBonusSettings() {
if (\Bitrix\Main\Loader::includeModule('itsector.bonus')) {
if (empty($this->order)) {
return;
}

$bonusPercent = (float)\Bitrix\Main\Config\Option::get('itsector.bonus', 'max_deduct_percent', 30);

$basketPriceForBonus = 0;
foreach ($this->order->getBasket() as $basketItem) {
if ($basketItem->getDiscountPrice() <= 0) {
$basketPriceForBonus += $basketItem->getFinalPrice();
}
}

$deliveryPrice = $this->order->getDeliveryPrice();
$orderPrice = $this->order->getPrice();

$maxAllowedByPercent = floor($basketPriceForBonus * ($bonusPercent / 100));
$maxWithoutDelivery = $orderPrice - $deliveryPrice;

$finalMaxSum = max(0, min($maxAllowedByPercent, $maxWithoutDelivery));

// Отдаем в JS
$this->arResult['JS_DATA']['BONUS_PERCENT'] = $bonusPercent;
$this->arResult['JS_DATA']['MAX_BONUS_SUM_ALLOWED'] = $finalMaxSum;
}
}

Фронтенд-защита (UX)

Чтобы покупатель физически не мог вписать в поле ввода цифру больше положенной, добавляем небольшой JS-скрипт в самый конец шаблона корзины (template.php):

BX.ready(function() {
// Используем делегирование, чтобы скрипт жил даже после AJAX-перезагрузки блоков
BX.bindDelegate(document.body, 'input', { tag: 'input', attribute: { name: 'PAY_CURRENT_ACCOUNT_VALUE' } }, function(e) {
var maxAllowed = 0;

// Достаем лимит, который мы рассчитали в class.php
if (BX.Sale && BX.Sale.OrderAjaxComponent && BX.Sale.OrderAjaxComponent.result) {
maxAllowed = parseFloat(BX.Sale.OrderAjaxComponent.result.MAX_BONUS_SUM_ALLOWED) || 0;
}

var currentValue = parseFloat(this.value) || 0;

// Если ввел больше разрешенного — жестко возвращаем к максимуму
if (currentValue > maxAllowed) {
this.value = maxAllowed;
// Дергаем событие change, чтобы Битрикс запустил пересчет корзины
BX.fireEvent(this, 'change');
}
});
});

Задача 2. Справедливое начисление бонусов (Без акций)

Несправедливо давать клиенту кэшбэк за товары, которые он и так купил с огромной скидкой. Идем в ядро бонусного модуля (BonusService.php) и правим метод расчета начислений calculateChargeForOrder.

Находим цикл перебора товаров в корзине и добавляем проверку:

foreach ($basketItems as $item) {
// Проверяем: 1. Нет ли системной скидки Битрикса И 2. Нет ли кастомного свойства IS_SALE в каталоге
if ($item->getDiscountPrice() <= 0 && !$this->isSaleItem($item->getProductId())) {
$order_sum_for_charge += $item->getFinalPrice();
}
}

Теперь база для начисления бонусов считается только из товаров за полную стоимость.

Задача 3. Сгорание бонусов при неактивности

Баллы — это отложенная скидка, и они должны мотивировать покупать чаще. Добавим агента, который будет аннулировать баланс клиента, если тот ничего не покупал больше 12 месяцев.

Добавляем этот метод в конец класса BonusService в файле BonusService.php:

/**
* Агент для аннулирования баллов при неактивности > 12 месяцев
*/
public static function expireOldBonusesAgent()
{
\Bitrix\Main\Loader::includeModule('sale');
$currency = self::CURRENCY;

// Вычисляем дедлайн
$dateLimit = (new \Bitrix\Main\Type\DateTime())->add('-12 months');

// Получаем всех пользователей с положительным балансом
$accountRes = \CSaleUserAccount::GetList(
[],
["CURRENCY" => $currency, ">CURRENT_BUDGET" => 0],
false, false, ["USER_ID", "CURRENT_BUDGET"]
);

while ($account = $accountRes->Fetch()) {
$userId = $account['USER_ID'];

// Ищем последний заказ пользователя
$lastOrder = \Bitrix\Sale\Order::getList([
'select' => ['DATE_INSERT'],
'filter' => ['USER_ID' => $userId],
'order' => ['DATE_INSERT' => 'DESC'],
'limit' => 1
])->fetch();

$shouldExpire = false;

if ($lastOrder) {
if ($lastOrder['DATE_INSERT'] < $dateLimit) {
$shouldExpire = true;
}
} else {
// Если заказов не было (бонусы за регу), смотрим дату регистрации
$user = \Bitrix\Main\UserTable::getRowById($userId);
if ($user && $user['DATE_REGISTER'] < $dateLimit) {
$shouldExpire = true;
}
}

// Списываем
if ($shouldExpire) {
\CSaleUserAccount::UpdateAccount(
$userId,
-$account['CURRENT_BUDGET'],
$currency,
"Списание баллов из-за отсутствия покупок более 12 месяцев"
);
}
}

return "\\ITSECTOR\\Sale\\BonusService::expireOldBonusesAgent();";
}

Для запуска агента нужно выполнить команду регистрации CAgent::AddAgent(...) в командной PHP-строке Битрикса.

Задача 4. Вывод реальной суммы к оплате в 1С:УНФ

Проблема: При обмене с сайтом 1С считает колонку «Сумма» как базовую стоимость документа. Списанные баллы приходят как частичная оплата или внутренний счет. Из-за этого менеджеры в общем списке заказов в 1С видят полную стоимость (например, 5000 руб.), хотя клиент оплатил половину бонусами и должен отдать курьеру всего 2500 руб.

Решение: Заставим Битрикс считать сумму без баллов и передавать её как отдельное текстовое свойство заказа, которое мы затем выведем в 1С.

Шаг 1. Создаем свойство в Битриксе

В админке переходим в свойства заказа и создаем служебное свойство (Тип: Строка) с символьным кодом TO_PAY_RUBLES.

Шаг 2. Пишем обработчик в init.php

В файл /local/php_interface/init.php добавляем код, который перед сохранением заказа посчитает разницу:

use Bitrix\Main\EventManager;
use Bitrix\Sale;

EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderBeforeSaved',
'updateToPayProperty'
);

function updateToPayProperty(\Bitrix\Main\Event $event)
{
/** @var Sale\Order $order */
$order = $event->getParameter("ENTITY");

$price = $order->getPrice(); // Общая сумма

// Ищем наши списанные баллы (внутренний счет)
$bonusSum = 0;
$paymentCollection = $order->getPaymentCollection();
if ($paymentCollection) {
foreach ($paymentCollection as $payment) {
if ($payment->isInner()) {
$bonusSum += $payment->getSum();
}
}
}

// Реальная сумма к оплате рублями
$toPay = $price - $bonusSum;

// Записываем результат в свойство
$propertyCollection = $order->getPropertyCollection();
if ($propertyCollection) {
$prop = $propertyCollection->getItemByOrderPropertyCode('TO_PAY_RUBLES');
if ($prop) {
$prop->setValue($toPay);
}
}
}

Шаг 3. Настройка обмена и 1С

  1. В Битриксе в профиле экспорта заказов связываем это свойство со свободным реквизитом.

  2. Делаем тестовый заказ и запускаем синхронизацию.

  3. В 1С:УНФ заходим в общий список заказов, нажимаем «Еще -> Изменить форму», находим наш новый дополнительный реквизит Сумма к оплате (руб) и выводим его отдельной колонкой рядом со стандартной.

Итог

Мы получили гибкую и защищенную от «хитрецов» корзину на стороне Битрикса, автоматизировали очистку базы от старых долгов по программе лояльности и сделали жизнь менеджеров в 1С:УНФ намного проще!

Поделись, если оказалось полезно :)
Нет комментариев

Написать