Меню

Автоматический повтор заказа на D7 со списанием с внутреннего счета

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

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

  • Предоставление услуг хостинга;
  • Доставка воды в офис;
  • Подписка на журнал;
  • Так же приходилось реализовывать магазин по еженедельной доставке новых носков;
  • и.т.д.

Пополнение баланса внутреннего счета

Здесь всё просто: в 1С-Битрикс уже существует компонент для этих целей – sale.account.pay (Добавление средств на счёт). Необходимо вывести его в кабинете пользователя и настроить под нужные суммы. Шаблон компонента достаточно простой и легко адаптируется под любой дизайн.

Пополнение баланса внутреннего счета

Скрипт для повтора заказа и автоматической оплаты со списанием с баланса

Ниже представлен скрипт, который можно запускать по cron с нужной периодичностью (например в 00:00 каждого дня).
Также его можно выполнять через агенты в системе (подробнее – Создание агента в 1С-Битрикс).

$_SERVER["DOCUMENT_ROOT"] = realpath(dirname(__FILE__)."/../..");
$DOCUMENT_ROOT = $_SERVER["DOCUMENT_ROOT"];
define("NO_KEEP_STATISTIC", true);
define("NOT_CHECK_PERMISSIONS",true);
define('BX_NO_ACCELERATOR_RESET', true);
define('CHK_EVENT', true);
define('BX_WITH_ON_AFTER_EPILOG', true);

require_once($_SERVER["DOCUMENT_ROOT"] . "/bitrix/modules/main/include/prolog_before.php");

@set_time_limit(0);
@ignore_user_abort(true);

use Bitrix\Main\Loader,
    Bitrix\Sale\Basket,
    Bitrix\Sale\Order,
    Bitrix\Currency\CurrencyManager;

Loader::includeModule('sale');

// Получаем дату неделю назад от текущей
$initialDate = new \Bitrix\Main\Type\Date();
$weekAgo = $initialDate->add('-1w');


// Получаем заказы, созданные неделю назад
$orderList = Order::getList([
    'filter' => [
        'DATE_INSERT' => $weekAgo,
        'STATUS' => 'F'
    ],
    'select' => [
        'ID',
        'USER_ID',
        'PRICE',
        'CURRENCY'
    ]
]);

while ($order = $orderList->fetch()) {
    // Получаем ID пользователя из заказа
    $userId = $order['USER_ID'];

    // Получаем сумму заказа
    $orderTotal = $order['PRICE'];

    // Получаем внутренний счет пользователя
    if (
        $bill = CSaleUserAccount::GetByUserID(
            $userId,
            $order['CURRENCY']
        )
    ) {
        $userBalance = $bill['CURRENT_BUDGET'];
        // Проверяем, достаточно ли средств на внутреннем счете
        // для оплаты заказа
        if ($userBalance >= $orderTotal) {
            // Повторяем заказ функция repeatOrder (сама она ниже)
            repeatOrder($order['ID']);
        } else {
            /*
            Денег на балансе не достаточно
            Можно отправить пользователю письмо
            с оповещением
            */
        }
    } else {
        /*
        У пользователя в принципе нет счета
        Лучше всего при регистрации в фоне счет создавать
        с нулем на балансе или с привественным бонусом
        CSaleUserAccount::Add
        */
    }

}


function repeatOrder($orderId)
{

    // Загружаем заказ по ID
    $loadOrder = Order::load($order);

    // Получаем данные о пользователе
    $userId = $loadOrder->getUserId();
    $userEmail = $loadOrder->getPropertyCollection()->getUserEmail(); // Получаем email пользователя
    $siteId = $loadOrder->getSiteId();
    $currencyCode = CurrencyManager::getBaseCurrency();

    // Очищаем корзину пользователя
    $basket = Sale\Basket::loadItemsForFUser(
            $userId,
            $siteId
    );
    foreach ($basket as $basketItem) {
        // Отложенные не трогаем
        if ($basketItem->getField('DELAY') === 'N') {
           // Удаляем запись
           $basket->getItemById($basketItem->getId())->delete();
           // Сохраняем корзину
           $basket->save();
        }
    }

    // Получаем корзину из заказа
    $orderBasket = $loadOrder->getBasket();
    
    // Тут можно добавить проверку на существование корзины
    // И если ее нет создать пустую
    // $basket = Basket::create($siteId);

    // Переносим товары из корзины старого заказа в новую корзину
    foreach ($orderBasket as $orderItem) {
        $productID = $orderItem->getProductId();
        $quantity = $orderItem->getQuantity();

        // Добавляем товар в новую корзину
        $basketItem = $basket->createItem('catalog', $productID);
        $basketItem->setFields(array(
            'QUANTITY' => $quantity,
            'CURRENCY' => $currencyCode,
            'PRODUCT_PROVIDER_CLASS' => '\Bitrix\Catalog\Product\CatalogProvider',
        ));
    }

    // Создаем новый заказ на основе новой корзины
    $newOrder = Order::create($siteId, $userId);
    $newOrder->setPersonTypeId(1); // Установите ID типа плательщика
    $newOrder->setBasket($basket);

    // Устанавливаем доставку с ID 1
    $shipmentCollection = $loadOrder->getShipmentCollection();
    $shipment = $shipmentCollection->createItem(
        Bitrix\Sale\Delivery\Services\Manager::getObjectById(1) // 1 - ID службы доставки
    );
    $shipmentItemCollection = $shipment->getShipmentItemCollection();
    foreach ($basket as $basketItem) {
        $item = $shipmentItemCollection->createItem($basketItem);
        $item->setQuantity($basketItem->getQuantity());
    }

    // Устанавливаем платежную систему с ID 4
    $paymentCollection = $newOrder->getPaymentCollection();
    $payment = $paymentCollection->createItem(
        Bitrix\Sale\PaySystem\Manager::getObjectById(1) // ID платежной системы
    );
    $payment->setField("SUM", $newOrder->getPrice());
    $payment->setField("CURRENCY", $newOrder->getCurrency());
    $paymentCollection[0]->setPaid('Y');


    // Сохраняем новый заказ
    $result = $newOrder->save();
    if (!$result->isSuccess()) {
        throw new \Exception("Не удалось создать новый заказ: " . implode(", ", $result->getErrorMessages()));
    }

    $orderId = $newOrder->getId();
    return $orderId; // Возвращаем ID нового заказа

}


require_once($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_after.php');

Данный скрипт получает список выполненных заказов (со статусом «F»), совершённых неделю назад и собирает данные о заказе (ID, ID пользователя, цену, валюту).

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

Функция repeatOrder().

  • По переданному ID загружается исходный заказ.
  • Из заказа извлекаются данные о пользователе, его email, сайт и базовая валюта.
  • Очищается текущая корзина пользователя что бы не было накладок и спорных ситуаций.
  • Получаем корзину исходного заказа и создаём новую корзину, в которую копируются все товары с сохранением количества.
  • Создаём новый заказ, которому присваиваем тип плательщика.
  • Затем устанавливаем доставку: выбирается служба доставки по ID 1 - это системная доставка "Без доставки". Подходит для услуг, но можете скопировать из предыдущего заказа или явно указать ID нужной доставки
  • Выполняем настройку платежной системы: выбирается система по ID 1, это всегда "Внутренний счет пользователя".
  • Оплата помечается как переключенная в "Оплачен", средства с внутреннего счета спишутся автоматически в пользу этого заказа
  • Новый заказ сохраняется. При отсутствии ошибок возвращается ID созданного заказа, что свидетельствует об успешном повторении заказа.
Михаил Базаров 12.04.2025
Да, в модуле SALE есть метод для повтора заказа (по сути дублирования корзины):
Код
// Загружаем заказ
$loadOrder = Order::load($orderId);
// Клонируем его
$newOrder = $loadOrder->createClone();
$newOrder->save();
// Выполняем остальные обработки
// Доставка, платежная система, оплата итд
return $newOrder->getId(); 

Но он не очищает текущую корзину. Как следствие оплата за повтор может оказаться больше чем стоимость предыдущего заказа, что приведет к конфликтным ситуациям

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

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

Разработка интернет-магазина с максимальной оптимизацией от 350 000 руб.
от 4-х недель

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

Запуск интернет-магазина на готовом решении от 60 000 руб.
от 7-ми дней

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

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

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

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