Меню

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

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

Задача: В магазине реализован внутренний счет (бонусный счет) для покупателей. Необходимо, чтобы при оплате заказа бонусами учитывалась только
стоимость товаров, а стоимость доставки не входила в сумму оплаты с внутреннего счета.

Использование внутреннего счета в качестве бонусного описано в записях блога:
Пополнение внутреннего счета из 1С
Бонус за заказ, на внутренний счет

В настройках платежной системы "Внутренний счет" установлено ограничение (штатная возможность 1С-Битрикс), что с него можно оплатить только 30% стоимости заказа.

После того как пользователь оформляет заказ, происходит его частичная оплата и заказ переводится в статус "Частично оплачен".

Это происходит, только если у пользователя не нулевой баланс внутреннего счета (далее по тексту "бонусный счет" или "бонусы") и если пользователь выбрал оплату бонусами. Остальные заказы создаются со статусом "Новый ожидается оплата" и при оплате сразу переходят в статус "Полностью оплачен".

Событие OnSaleStatusOrder, меняем сумму оплаченную с внутреннего счета

На текущий момент, при выборе оплаты бонусами, автоматически списывается 30% от всей суммы заказа (товары + доставка), что не соответствует потребности.

Нужно перехватить событие изменения статуса заказа на "Частично оплачен", отменить предыдущую оплату бонусами (которая учитывает доставку), и повторно списать бонусы только за товары.

Добавляем обработчик события OnSaleStatusOrder (изменение статуса заказа) в init.php:

AddEventHandler('sale', 'OnSaleStatusOrder', 'UpdateOrderPayment');
function UpdateOrderPayment($orderID, &$arFields)
{
    // Загружаем заказ по его ID
    $order = Bitrix\Sale\Order::load($orderID);
    // Проверяем, что статус заказа сменился на 'P' (частичная оплата)
    // и что заказ еще полностью не оплачен
    if (
        ($arFields == 'P') and
        (!$order->isPaid())
    ) {

        // Получаем коллекцию товаров в заказе (корзину)
        $basket = $order->getBasket();
        $itemsTotalPrice = 0;
        // Подсчитываем общую стоимость товаров в корзине
        foreach ($basket as $basketItem) {
            $itemsTotalPrice += $basketItem->getPrice() * $basketItem->getQuantity();
        }

        // Получаем коллекцию оплат
        $paymentCollection = $order->getPaymentCollection();

        // Получение стоимости доставки
        $deliveryPrice = $order->getDeliveryPrice();
        $totalBonusPaid = 0;

        $dbRes = \Bitrix\Sale\PaymentCollection::getList(
            [
                'select' => [
                    'ID',
                    'PAY_SYSTEM_ID'
                ],
                'filter' => [
                    // Можно тут явно получить по ID => 1
                    '=ORDER_ID' => $orderID,
                ]
            ]
        );
        while ($item = $dbRes->fetch()) {
            // Если явно получаем в фильтре по ID => 1 можно не городить лишний if, достаточно проверить на не пустоту результата
            // Если среди оплат есть оплата бонусами
            if ($item['PAY_SYSTEM_ID'] == 1) {

                // Получаем именно эту оплату $item['ID'] - это ID оплаты а не платежной системы!!!
                $payment = $paymentCollection->getItemById($item['ID']);

                // Возращаем оплату
                $r = $payment->setReturn('Y');
                // Нужно сохранить иначе деньги не вернуться на бонусный счет
                $order->save();

                // Рассчитываем 30% от стоимости товаров для бонусного платежа
                $bonusPart = $itemsTotalPrice * 0.3;
                $totalBonusPaid = $bonusPart;

                // Получаем текущий баланс бонусных средств пользователя
                $userCurrentAccount = CSaleUserAccount::GetByUserID(
                    $order->getUserId(),
                    "RUB" // Валюта бонусных счетов
                );

                // Если бонусов меньше, чем требуется к списанию, списываем всё доступное
                if ($totalBonusPaid > $userCurrentAccount['CURRENT_BUDGET']) {
                    $totalBonusPaid = $userCurrentAccount['CURRENT_BUDGET'];
                }

                // В стоимость оплаты внутреннего счета ставим суммы использованных бонусов
                $result = $payment->setFields([
                    'SUM' => $totalBonusPaid,
                    'PAID' => 'Y', // Устанавливаем статус как оплаченный
                ]);
                
                $paydFromBonus = true;

            } else {
                if ($paydFromBonus) {
                    $payment = $paymentCollection->getItemById($item['ID']);
                    // Остальные платежи - оставшееся значение
                    $remainingSum = $itemsTotalPrice - $totalBonusPaid + $deliveryPrice;
                    $payment->setFields([
                        'PAID' => 'N',
                        'SUM' => $remainingSum,
                    ]);
                }
            }
        }
        // Сохраняем заказ
        $order->save();
    }
}

Получили стомость всех товаров в корзине заказа, высчитали из нее 30%, отменив предыдущую оплату бонусами (товары + доставка) изменили сумму оплаты только за товары и сделали эту оплату оплаченной.

Остальной платеж (платежную сиситему) пересчитали с учетом оплаченного бонусами.

В методе \Bitrix\Sale\PaymentCollection::getList мы находим бонусный счет по ID системы оплаты, он всегда первый, так как эта система создается при установке Битрикс.

Но, если по каким-то соображениям, вы создали свою систему оплаты с внутреннего счета, найти его ID можно так:

$innerPaysysId = \Bitrix\Sale\PaySystem\Manager::getInnerPaySystemId()
echo $innerPaysysId;

Перерасчет оплаты бонусами на этапе офомления заказа

Позже, добавлю в эту заметку вариант без использования init.php (в целом не любитель добавлять в него логику, если можно обойтись). Можно тоже самое сделать на уровне компонента/шаблона sale.order.ajax.

Коротко: суть в том что в файле confirm.php мы уже знаем ID оформленного заказа и можем получить факт использования бонусного (внутреннего) счета. По этому всю "работу" можем провернуть прямо здесь.

По большому счету, ровно теми же методами, что приведены выше. Просто перебрав платежные системы заказа:

// Узнаем была ли оплата с внутреннего смета?
    // Это черновик для блога - позже сделаю нормально
    $innerPaysysId = \Bitrix\Sale\PaySystem\Manager::getInnerPaySystemId()
    $dbRes = \Bitrix\Sale\PaymentCollection::getList(
    [
        'select' => [
            'ID',
            'PAY_SYSTEM_ID'
        ],
        'filter' => [
            '=ORDER_ID' => $arResult['ORDER_ID'],
            "PAY_SYSTEM_ID" => $innerPaysysId,
        ]
    ]
);
while ($item = $dbRes->fetch()) {
    // Если внутренний счет
    // Логика обработки и изменения платежки
}
Михаил Базаров 22.05.2025
Для шаблона оформления заказа, что бы показать пользователю правильные расчеты.
В блоке Итого модифицируем данные (в result_modifier)
Код
<?
// Если выбрана оплата бонусами иначе этого блока и не видно
if (!empty($arResult['PAYED_FROM_ACCOUNT_FORMATED'])) {
    $totalBonusPaid = $arResult['JS_DATA']['TOTAL']['ORDER_PRICE'] * 0.3;
    // Доп проверка - если у пользователя бонусов меньше чем $totalBonusPaid
    $userCurrentAccount = CSaleUserAccount::GetByUserID(
        $USER->GetID(),
        "RUB"
    );
    if ($totalBonusPaid > $userCurrentAccount['CURRENT_BUDGET']) {
        $totalBonusPaid = $userCurrentAccount['CURRENT_BUDGET'];
    }
    // Итого будет оплачено бонусами
    echo $totalBonusPaid . ' ₽';

   // Итого за минусом бонусов и плюс доствка
   $arResult['JS_DATA']['TOTAL']['ORDER_PRICE'] = $arResult['JS_DATA']['TOTAL']['ORDER_PRICE'] - $totalBonusPaid + $arResult['JS_DATA']['TOTAL']['DELIVERY_PRICE'];

}
?>

Стоимость и сроки разработки сайтов и приложений

Окончательная стоимость и сроки разработки сайта формируются после обсуждения деталей на этапе заказа. Как правило, они редко выходят за обозначенные ниже рамки.

Интернет-магазин: индивидуальная разработка от 350 000 руб.
от 5-ти недель

Cоздание интернет-магазина на 1С-Битрикс. Разработка с нуля, оптимизация кода и производительности под конкретный проект и требования. Реализация любого функционала без ограничений готовых решений.

Интернет-магазин: на готовом решении от 60 000 руб.
от 7-ми дней

Готовое решение — вариант для тех, кто не хочет тратить много средств на индивидуальный проект, и не имеет серьезных требований к сайту. Магазин, быстро запускается на базе одного из 200-та готовых решений

Мобильное приложение от 400 000 руб.
от 1-го месяца

Разработка кроссплатформенного мобильного приложения, которое не уступает нативным решениям как в производительности, так и пользовательском опыте. Публикуется в AppStore, GooglePlay и RuStore

Опросник на разработку. После ознакомления, задам уточняющие вопросы и оценю проект по стоимости и срокам разработки.