Бонус за выполненный заказ на внутренний счет пользователя

Просмотров: 7009

Задача: после того как заказ, в интернет-магазине, перешел в статус "Выполнен" начислить пользователю 5% от стоимости заказа, на накопительный счет - с которого можно оплатить будущие заказы.

Для этого нам нужно событие смены статуса заказа OnSaleStatusOrder или (вариант в новом ядре) OnSaleStatusOrderChange и метод CSaleUserAccount::UpdateAccount изменяющий сумму на счете пользователя

Добавим следующий код в init.php, пояснения ниже

use Bitrix\Main\Loader;
AddEventHandler('sale', 'OnSaleStatusOrder', 'OrderComplete');
function OrderComplete($orderID, &$arFields)
{
    if ($arFields == 'F') {
        Loader::includeModule('sale');
        $order = \Bitrix\Sale\Order::load($orderID);
        $orderUser = $order->getUserId();
        $orderSumm = $order->getPrice();
        $bonusPercent = orderSumm * 5 / 100;
        CSaleUserAccount::UpdateAccount(
            $orderUser,
            $bonusPercent,
            'RUB',
            'За оформленный заказ',
            $orderID,
            false
        );
    }
}
  • Подключили модуль sale Loader::includeModule('sale');;
  • Проверили, что статус заказ перешел в выполнен if ($arFields == 'F') ;
  • Зная id заказа, из переменной $orderID, получили ID пользователя $orderUser = $order->getUserId(); и сумму заказа $orderSumm = $order->getPrice();;
  • Высчитали 5% из стоимости заказа $bonusPercent;
  • Методом CSaleUserAccount::UpdateAccount начислил пользователю сумму бонуса, основанием сделали ID заказа;

В личном кабинете можно вывести историю начислений и списаний, например так:

Бонус пользователям на заказ

Начислить процент без учета стоимости доставки и без учета оплаты с накопительного счета

Расширяем задачу: нужно начислять 10% но при этом учитывать только стоимость товаров, без учета стоимости доставки. Дополнительно, не учитываем полную или частичную оплату с внутреннего счета. То есть: начисляем бонус, только на реально потраченные деньги.

AddEventHandler('sale', 'OnSaleStatusOrder', 'OrderComplete');
function OrderComplete($orderID, &$arFields)
{
    if ($arFields == 'F') {
        Loader::includeModule('sale');
        $order = \Bitrix\Sale\Order::load($orderID);
        $orderUser = $order->getUserId();
        $orderSumm = $order->getPrice();
        $orderDeliveryPrice = $order->getDeliveryPrice();
        $orderPayments = \Bitrix\Sale\PaymentCollection::getList([
            'select' => ['SUM'],
            'filter' => [
                '=ORDER_ID' => $orderID,
                '=PAY_SYSTEM_ID' => 1,
                '=PAID' => 'Y'
            ]
        ]);
        while ($orderPayment = $orderPayments->fetch()) {
            $orderPayid = $orderPayment['SUM'];
        }
        // Общая сумма минус доставка и минус накопительный счет
        if (empty($orderPayid)) {
            $realOrderPrice = $orderSumm - $orderDeliveryPrice;
        } else {
            $realOrderPrice = $orderSumm - $orderDeliveryPrice - $orderPayId;
        }
        $bonusPercent = $realOrderPrice * 10 / 100;
        CSaleUserAccount::UpdateAccount(
            $orderUser,
            $bonusPercent,
            'RUB',
            'За оформленный заказ',
            $orderID,
            'За заказ №' . $orderID,
        );
    }
}

Дополнительно, к коду из первого варианта добавили:

  • Получили стоимость доставки $orderDeliveryPrice = $order->getDeliveryPrice();;
  • Получили коллекцию оплат для заказа $orderPayments = \Bitrix\Sale\PaymentCollection::getList, отфильтровав только оплату с внутреннего счета с 'select' => ['SUM'], - так как нам нужн только сумма оплаты;
  • В переменной $realOrderPrice высчитали сумму заказа за минусом стоимости доставки и оплаты с накопительного счета;
Пример реализации данного функционала, в одной из серий видеокурса по разработке проекта на 1С-Битрикс Перейти в серию

Усложнение. Процент для начисления в зависимости от суммы заказа

Расширяем задачу, дальше: начислять разный процент, на внутренний счет, в зависимости от стоимости заказа.

Создаем и заполняем HL инфоблок с пользовательскими полями:

  • 'UF_BONUS_FROM' - Стоимость заказа от
  • UF_BONUS_TO' - Стоимость заказа до
  • UF_BONUS_PRICE' - Величина процента начисления

После наполнения:

Бонус пользователям на заказ

И расширяем код из примеров выше:

use Bitrix\Main\Loader;
use Bitrix\Highloadblock as HL;
use Bitrix\Main\Entity;
AddEventHandler('sale', 'OnSaleStatusOrder', 'OrderComplete');
function OrderComplete($orderID, &$arFields)
{
    if ($arFields == 'F') {
        Loader::includeModule('sale');
        Loader::includeModule('highloadblock');
        $order = \Bitrix\Sale\Order::load($orderID); // 123 - ID заказа
        $orderUser = $order->getUserId();
        $orderSumm = $order->getPrice();
        $orderDeliveryPrice = $order->getDeliveryPrice();
        $orderPayments = \Bitrix\Sale\PaymentCollection::getList([
            'select' => ['SUM'],
            'filter' => [
                '=ORDER_ID' => $orderID,
                '=PAY_SYSTEM_ID' => 13,
                '=PAID' => 'Y'
             ]
        ]);
        while ($orderPayment = $orderPayments->fetch()) {
            $orderPayid = $orderPayment['SUM'];
        }
        if (empty($orderPayid)) {
            $orderPriceBonus = $orderSumm - $orderDeliveryPrice;
        } else {
            $orderPriceBonus = $orderSumm - $orderDeliveryPrice - $orderPayid;
        }
        $hlblockDatas = HL\HighloadBlockTable::getById(5)->fetch();
        $entityHlBonus = HL\HighloadBlockTable::compileEntity($hlblockDatas);
        $entityDataClassBonus = $entityHlBonus->getDataClass();
        $bonusData = $entityDataClassBonus::getList(array(
            'select' => array('UF_BONUS_FROM', 'UF_BONUS_TO', 'UF_BONUS_PRICE'),
        ));
        while ($arBonusData = $bonusData->Fetch()) {
            $priceFrom = $arBonusData['UF_BONUS_FROM'];
            $priceTo = $arBonusData['UF_BONUS_TO'];
            if (($orderPriceBonus > $priceFrom) && ($orderPriceBonus < $priceTo)) {
                $priceBonus = $arBonusData['UF_BONUS_PRICE'];
                $summToAddBonus = $orderPriceBonus * $priceBonus / 100;
                CSaleUserAccount::UpdateAccount(
                    $orderUser,
                    $summToAddBonus,
                    'RUB',
                    'За заказ №' . $orderID,
                    $orderID,
                    false
                );
            }
        }
    }
}
  • Подключили модуль Hl инфоблоков Loader::includeModule('highloadblock');
  • Минимальное и максимальное значения, из HLблока загоняем в переменные $priceFrom и $priceTo
  • Получили значения пользовательских полей инфоблока и отсекли диапазаон подпадающий под условия начисления
    if (($orderPriceBonus > $priceFrom) && ($orderPriceBonus < $priceTo))

Дополнено: использование нового подхода с отключенными устаревшими событиями

Для переписывания кода на использование новых событий Битрикс, нам потребуется использовать событие OnSaleOrderSaved:

use Bitrix\Main\Loader;
use Bitrix\Main\EventManager;
use Bitrix\Main\EventResult;

EventManager::getInstance()->addEventHandler(
    'sale',
    'OnSaleOrderSaved',
    'OrderComplete'
);

function OrderComplete(\Bitrix\Main\Event $event)
{
    $order = $event->getParameter('ENTITY');

    if ($order instanceof \Bitrix\Sale\Order) {
        $arFields = $order->getFieldValues();
        $orderID = $arFields['ID'];

        // Теперь можно продолжить выполнение кода, как и ранее
        Loader::includeModule('sale');
        Loader::includeModule('highloadblock');
        
        if ($arFields['STATUS_ID'] == 'F') {
            // Код из примеров выше
        }
    }

    return new EventResult(EventResult::SUCCESS);
}

В этой версии кода я заменил AddEventHandler на использование EventManager, и заменил событие на OnSaleOrderSaved, так как оно позволяет нам получить объект заказа без необходимости его загрузки.

Обратите внимание, что в этой версии кода параметры события передаются через $event->getParameter('ENTITY'), и мы можем проверить, является ли объект $order экземпляром Bitrix\Sale\Order.

Михаил Базаров 12.11.2023
Вывод истории начислений списаний, используем метод: CSaleUserTransact::GetList
Метод возвращает результат выборки записей транзакций в соответствии со своими параметрами. Нестатический метод.

CDBResult
CSaleUserTransact::GetList(
    array arOrder = array(),
    array arFilter = array(),
    array arGroupBy = false,
    array arNavStartParams = false,
    array arSelectFields = array()
);

Код
<table class="trans_history">
    <tr>
        <td width="200">Дата:</td>
        <td width="200">Сумма:</td>
        <td>Основание:</td>
    </tr>
    <?
    global $USER;
    $USER->GetID();
    CModule::IncludeModule("sale");
    $res = CSaleUserTransact::GetList(Array("ID" => "DESC"), array("USER_ID" => $USER->GetID()));
    while ($arFields = $res->Fetch()) {
//            echo '<pre>';
//            print_r($arFields);
//            echo '</pre>';
        ?>
        <tr>
            <td>
                <?= $arFields["TRANSACT_DATE"] ?>
            </td>
            <td>
                <?= ($arFields["DEBIT"] == "Y") ? "+" : "-" ?>
                <?= SaleFormatCurrency($arFields["AMOUNT"], $arFields["CURRENCY"]) ?>
                <small>(<?= ($arFields["DEBIT"] == "Y") ? "начислено" : "списано" ?>)</small>
            </td>
            <td>
                <?
                if (empty($arFields["NOTES"])) {
                    if (empty($arFields["ORDER_ID"])) {
                        echo 'Основание не найдено';
                    } else {
                        $arOrder = CSaleOrder::GetByID($arFields["ORDER_ID"]);
                        echo 'Заказ №' . $arOrder['ACCOUNT_NUMBER'];
                    }
                } else {
                    echo $arFields["NOTES"];
                } ?>
            </td>
        </tr>
    <? } ?>
</table>
Евгений 15.11.2023
Подскажите, а как просто массово пополнить счета большому числу пользователей по ID?

И еще... Можно ли связать внутренний счет с ифоблоком?
Михаил Базаров 16.11.2023
Массово пополнить: можно просто пробежавшись по пользователямв цикле

Код
$rsUsers = CUser::GetList(
   false, 
   false, 
   false, // Это фильтр, можно выбрать определнных пользователей, например диапазон ID или дней рождений
); // выбираем пользователей

while($arRes = $rsUsers->Fetch()){ // Проходим циклом по отобранным пользователям
      CSaleUserAccount::UpdateAccount(
            $arRes['ID'],
            '1000',
            'RUB',
            'Причина такой щедрости',
            false,
            false,
        );
}

А про связь с инфоблоком, нужны подробности, что бы подсказать что-то
Гость 16.11.2023
Спасибо, Михаил. Просто как-то связать с инфоблоком сумму на счете, чтобы через файл подгружать по ID пользователя.