Setting up automatic processing of 1C-Bitrix returns

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Configuring Automatic Return Processing in 1C-Bitrix

Return automation is needed when the volume exceeds 20–30 requests per day and managers are spending significant time on routine operations: checking order payment, creating a return in 1C, processing the refund, sending a customer email. Each of these actions can be triggered automatically when a request status changes — through sale module event handlers and Bitrix agents.

Return event model

The sale module generates events when returns are processed. Key automation trigger points:

  • OnSaleOrderReturnSaved — return request created or updated
  • OnSaleOrderReturnStatusChange — status changed
  • OnSalePaymentCollectionReturnAdd — payment return added

Register handlers in /local/php_interface/init.php:

$eventManager = \Bitrix\Main\EventManager::getInstance();

$eventManager->addEventHandler('sale', 'OnSaleOrderReturnSaved',
    [\Local\Returns\AutoProcessor::class, 'onReturnSaved']);

$eventManager->addEventHandler('sale', 'OnSaleOrderReturnStatusChange',
    [\Local\Returns\AutoProcessor::class, 'onStatusChange']);

Automatic actions when a request is created

namespace Local\Returns;

class AutoProcessor
{
    public static function onReturnSaved(\Bitrix\Main\Event $event): void
    {
        $return = $event->getParameter('ENTITY');
        if (!$return instanceof \Bitrix\Sale\OrderReturn) return;

        // Only on creation (not on update)
        if (!$event->getParameter('IS_NEW')) return;

        $returnId = $return->getId();

        // 1. Assign a responsible manager by rotation
        self::assignResponsible($returnId);

        // 2. Send confirmation to the customer
        Notifications::sendToCustomer($returnId, 'RETURN_CREATED');

        // 3. Notify the manager
        Notifications::sendToManager($returnId, 'NEW_RETURN');

        // 4. If the return amount is small — auto-approve
        self::tryAutoApprove($return);
    }

    public static function onStatusChange(\Bitrix\Main\Event $event): void
    {
        $returnId  = $event->getParameter('RETURN_ID');
        $newStatus = $event->getParameter('NEW_STATUS_ID');
        $oldStatus = $event->getParameter('OLD_STATUS_ID');

        $handler = new self();
        $handler->handleStatusTransition($returnId, $oldStatus, $newStatus);
    }

    private function handleStatusTransition(int $returnId, string $from, string $to): void
    {
        match ($to) {
            'APPROVED' => $this->onApproved($returnId),
            'RECEIVED' => $this->onReceived($returnId),
            'REJECTED' => $this->onRejected($returnId),
            'REFUND'   => $this->onRefunded($returnId),
            default    => null,
        };
    }
}

Auto-approval of small returns

Returns up to a configurable amount threshold do not require manual review:

private static function tryAutoApprove(\Bitrix\Sale\OrderReturn $return): void
{
    $autoApproveLimit = (float)\Bitrix\Main\Config\Option::get(
        'local.returns', 'auto_approve_limit', 500
    );

    $returnAmount = (float)$return->getField('REFUND_AMOUNT');

    if ($returnAmount <= 0 || $returnAmount > $autoApproveLimit) {
        return;
    }

    // Check: customer has no history of disputed returns
    $userId = $return->getOrder()->getUserId();
    if (self::hasDisputeHistory($userId)) {
        return;
    }

    // Automatically change status to APPROVED
    $return->setField('STATUS_ID', 'APPROVED');
    $return->setField('MANAGER_COMMENT', 'Auto-approved: amount below ' . $autoApproveLimit);
    $result = $return->save();

    if ($result->isSuccess()) {
        \CEventLog::Add([
            'SEVERITY'      => 'INFO',
            'AUDIT_TYPE_ID' => 'RETURN_AUTO_APPROVED',
            'MODULE_ID'     => 'local.returns',
            'DESCRIPTION'   => "Return #{$return->getId()} auto-approved, amount: {$returnAmount}",
        ]);
    }
}

private static function hasDisputeHistory(int $userId): bool
{
    // Count rejected returns over the last 6 months
    $dateFrom = new \Bitrix\Main\Type\Date();
    $dateFrom->add('-180 days');

    $result = \Bitrix\Sale\OrderReturnTable::getList([
        'filter' => [
            '=ORDER.USER_ID' => $userId,
            'STATUS_ID'      => 'REJECTED',
            '>=DATE_INSERT'  => $dateFrom,
        ],
        'select' => ['ID'],
    ]);

    return (bool)$result->fetch();
}

Automatic refund upon approval

When transitioning to "Approved" status — automatically initiate a refund via the payment system API:

private function onApproved(int $returnId): void
{
    $return = \Bitrix\Sale\OrderReturn::loadById($returnId);
    if (!$return) return;

    $order    = $return->getOrder();
    $payments = $order->getPaymentCollection();

    foreach ($payments as $payment) {
        if (!$payment->isPaid()) continue;

        $psHandler = \Bitrix\Sale\PaySystem\Manager::getObjectById(
            $payment->getPaymentSystemId()
        );

        if (!$psHandler) continue;

        $supportsRefund = method_exists($psHandler, 'refund');
        if (!$supportsRefund) {
            // Payment system does not support auto refund — queue for manual processing
            $this->flagForManualRefund($returnId, 'PS does not support auto refund');
            continue;
        }

        $refundAmount = (float)$return->getField('REFUND_AMOUNT');
        $result       = $psHandler->refund($payment, $refundAmount);

        if ($result->isSuccess()) {
            $return->setField('STATUS_ID', 'REFUND');
            $return->setField('REFUND_DATE', new \Bitrix\Main\Type\DateTime());
            $return->save();

            Notifications::sendToCustomer($returnId, 'RETURN_REFUNDED');
        } else {
            $this->flagForManualRefund($returnId, implode('; ', $result->getErrorMessages()));
        }
    }
}

Automatic receipt confirmation via tracking

If the customer ships the item back with a tracking number, delivery can be monitored automatically:

class TrackingWatcher
{
    // Agent running every 2 hours
    public static function checkPendingReturns(): string
    {
        $returns = \Bitrix\Sale\OrderReturnTable::getList([
            'filter' => [
                'STATUS_ID'  => 'APPROVED',
                'UF_TRACKING' => ['!=', '', false], // requests with a tracking number
            ],
            'select' => ['ID', 'UF_TRACKING', 'UF_CARRIER'],
        ]);

        $checker = new \Local\Delivery\TrackingChecker();

        while ($row = $returns->fetch()) {
            $status = $checker->getStatus($row['UF_TRACKING'], $row['UF_CARRIER'] ?? 'pochta');

            if ($status === 'delivered') {
                $return = \Bitrix\Sale\OrderReturn::loadById($row['ID']);
                $return->setField('STATUS_ID', 'RECEIVED');
                $return->save();
            }
        }

        return 'checkPendingReturns();'; // reschedule agent
    }
}

Escalation of overdue requests

Agent for SLA monitoring of returns:

class SlaWatcher
{
    private const SLA_HOURS = [
        'WAIT'   => 24,  // review within 24 hours
        'REVIEW' => 48,  // make a decision within 48 hours
    ];

    public static function checkOverdue(): string
    {
        foreach (self::SLA_HOURS as $statusId => $maxHours) {
            $deadline = new \Bitrix\Main\Type\DateTime();
            $deadline->add('-' . $maxHours . ' hours');

            $overdueReturns = \Bitrix\Sale\OrderReturnTable::getList([
                'filter' => [
                    'STATUS_ID'    => $statusId,
                    '<=DATE_STATUS' => $deadline,
                ],
                'select' => ['ID', 'RESPONSIBLE_ID', 'ORDER_ID'],
            ]);

            while ($row = $overdueReturns->fetch()) {
                Notifications::escalate($row['ID'], $row['RESPONSIBLE_ID'], $statusId, $maxHours);
            }
        }

        return 'checkOverdue();';
    }
}

Scope of work

  • Event handlers: OnSaleOrderReturnSaved, OnSaleOrderReturnStatusChange
  • Auto-approval logic based on amount and customer history
  • Automatic refund via payment system APIs (YooKassa, Tinkoff, etc.)
  • Incoming parcel tracking agent (Russian Post, CDEK, Boxberry)
  • SLA monitoring agent with escalation of overdue requests
  • Email automation: templates for each status

Timeline: basic automation (notifications + automatic refund) — 2–3 weeks. Full stack with SLA monitoring and tracking — 4–6 weeks.