Setting up return statuses in 1C-Bitrix

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 Return Statuses in 1C-Bitrix

Return statuses in Bitrix are not simply database labels. The way they are configured determines the entire business process: what the customer sees, what actions are available to the manager at each stage, and when notifications and integrations with external systems are triggered automatically. The standard out-of-the-box set is minimal and does not cover real-world scenarios.

Status management: where it lives

Return statuses are stored in the b_sale_order_return_status table and managed through the \CSaleOrderReturnStatus class. Admin interface: Online Store → Return Statuses.

Status fields:

  • ID — string identifier (WAIT, REVIEW, APPROVED, etc.)
  • NAME — display name
  • DESCRIPTION — description for internal use
  • SORT — display order
  • COLOR — label colour in the interface (hex)
  • NOTIFY — flag: whether to notify the customer when transitioning to this status
  • TEMPLATE — email notification template

Creating a custom status set

// /local/install/return_statuses.php — status installation script
$statuses = [
    [
        'ID'          => 'WAIT',
        'NAME'        => 'Pending Review',
        'DESCRIPTION' => 'Request received, not yet processed',
        'SORT'        => 100,
        'COLOR'       => '#f0ad4e',
        'NOTIFY'      => 'N',
    ],
    [
        'ID'          => 'REVIEW',
        'NAME'        => 'Under Review',
        'DESCRIPTION' => 'Manager is reviewing the request',
        'SORT'        => 200,
        'COLOR'       => '#5bc0de',
        'NOTIFY'      => 'Y',
        'TEMPLATE'    => 'RETURN_STATUS_REVIEW',
    ],
    [
        'ID'          => 'NEED_DOCS',
        'NAME'        => 'Documents Required',
        'DESCRIPTION' => 'Additional documents or photos have been requested',
        'SORT'        => 250,
        'COLOR'       => '#d9534f',
        'NOTIFY'      => 'Y',
        'TEMPLATE'    => 'RETURN_STATUS_NEED_DOCS',
    ],
    [
        'ID'          => 'APPROVED',
        'NAME'        => 'Approved',
        'DESCRIPTION' => 'Return approved, awaiting item shipment',
        'SORT'        => 300,
        'COLOR'       => '#5cb85c',
        'NOTIFY'      => 'Y',
        'TEMPLATE'    => 'RETURN_STATUS_APPROVED',
    ],
    [
        'ID'          => 'RECEIVED',
        'NAME'        => 'Item Received',
        'DESCRIPTION' => 'Warehouse has accepted the returned item',
        'SORT'        => 400,
        'COLOR'       => '#337ab7',
        'NOTIFY'      => 'Y',
        'TEMPLATE'    => 'RETURN_STATUS_RECEIVED',
    ],
    [
        'ID'          => 'REFUND',
        'NAME'        => 'Refunded',
        'DESCRIPTION' => 'Payment has been processed',
        'SORT'        => 500,
        'COLOR'       => '#3c763d',
        'NOTIFY'      => 'Y',
        'TEMPLATE'    => 'RETURN_STATUS_REFUND',
    ],
    [
        'ID'          => 'EXCHANGE',
        'NAME'        => 'Exchange',
        'DESCRIPTION' => 'Item exchanged instead of a refund',
        'SORT'        => 450,
        'COLOR'       => '#8a6d3b',
        'NOTIFY'      => 'Y',
        'TEMPLATE'    => 'RETURN_STATUS_EXCHANGE',
    ],
    [
        'ID'          => 'REJECTED',
        'NAME'        => 'Rejected',
        'DESCRIPTION' => 'Return rejected',
        'SORT'        => 600,
        'COLOR'       => '#a94442',
        'NOTIFY'      => 'Y',
        'TEMPLATE'    => 'RETURN_STATUS_REJECTED',
    ],
];

foreach ($statuses as $statusData) {
    // Check whether it already exists
    $existing = \CSaleOrderReturnStatus::GetByID($statusData['ID']);
    if ($existing) {
        \CSaleOrderReturnStatus::Update($statusData['ID'], $statusData);
    } else {
        \CSaleOrderReturnStatus::Add($statusData);
    }
}

Status transition matrix

Not all transitions should be permitted. For example, it should not be possible to transition from "Refunded" back to "Pending Review". Implement a matrix of allowed transitions:

namespace Local\Returns;

class StatusTransitionMatrix
{
    // [current status] => [allowed next statuses]
    private const ALLOWED_TRANSITIONS = [
        'WAIT'      => ['REVIEW', 'REJECTED'],
        'REVIEW'    => ['NEED_DOCS', 'APPROVED', 'REJECTED'],
        'NEED_DOCS' => ['REVIEW', 'REJECTED'],
        'APPROVED'  => ['RECEIVED', 'EXCHANGE'],
        'RECEIVED'  => ['REFUND', 'EXCHANGE'],
        'REFUND'    => [],       // terminal status
        'EXCHANGE'  => [],       // terminal status
        'REJECTED'  => ['WAIT'], // decision can be reconsidered
    ];

    // Transitions available to administrators only
    private const ADMIN_ONLY = [
        'REJECTED' => ['WAIT'],
    ];

    public function canTransition(string $from, string $to, bool $isAdmin = false): bool
    {
        $allowed = self::ALLOWED_TRANSITIONS[$from] ?? [];

        if (!in_array($to, $allowed, true)) {
            return false;
        }

        // Check role restrictions
        if (isset(self::ADMIN_ONLY[$from]) && in_array($to, self::ADMIN_ONLY[$from], true)) {
            return $isAdmin;
        }

        return true;
    }

    public function getAvailableTransitions(string $from, bool $isAdmin = false): array
    {
        $transitions = self::ALLOWED_TRANSITIONS[$from] ?? [];

        if (!$isAdmin) {
            $adminOnly = self::ADMIN_ONLY[$from] ?? [];
            $transitions = array_diff($transitions, $adminOnly);
        }

        return $transitions;
    }
}

Transition validation on status change

// Intercept status change attempts
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
    'sale',
    'OnBeforeSaleOrderReturnStatusChange',
    function (\Bitrix\Main\Event $event) {
        $newStatus = $event->getParameter('STATUS_ID');
        $return    = $event->getParameter('ENTITY');
        $oldStatus = $return->getField('STATUS_ID');

        $isAdmin = \CUser::IsAdmin();
        $matrix  = new \Local\Returns\StatusTransitionMatrix();

        if (!$matrix->canTransition($oldStatus, $newStatus, $isAdmin)) {
            $result = new \Bitrix\Main\EventResult(
                \Bitrix\Main\EventResult::ERROR,
                "Transition from '{$oldStatus}' to '{$newStatus}' is not permitted"
            );
            return $result;
        }

        // Data validation for specific statuses
        if ($newStatus === 'APPROVED') {
            if (!$return->getField('REFUND_AMOUNT')) {
                return new \Bitrix\Main\EventResult(
                    \Bitrix\Main\EventResult::ERROR,
                    "Please specify the refund amount before approving"
                );
            }
        }

        if ($newStatus === 'REJECTED') {
            if (!$return->getField('MANAGER_COMMENT')) {
                return new \Bitrix\Main\EventResult(
                    \Bitrix\Main\EventResult::ERROR,
                    "A reason must be provided when rejecting"
                );
            }
        }
    }
);

Localisation: status names in multiple languages

For multilingual sites, the customer-facing status name is taken from a language file:

// /local/lang/ru/lib/returns/status_labels.php
$MESS['RETURN_STATUS_WAIT']      = 'Ожидает рассмотрения';
$MESS['RETURN_STATUS_REVIEW']    = 'На рассмотрении';
$MESS['RETURN_STATUS_NEED_DOCS'] = 'Требуются документы';
$MESS['RETURN_STATUS_APPROVED']  = 'Одобрен';
$MESS['RETURN_STATUS_RECEIVED']  = 'Товар получен';
$MESS['RETURN_STATUS_REFUND']    = 'Деньги возвращены';
$MESS['RETURN_STATUS_EXCHANGE']  = 'Обмен';
$MESS['RETURN_STATUS_REJECTED']  = 'Отклонён';

// /local/lang/en/lib/returns/status_labels.php
$MESS['RETURN_STATUS_WAIT']      = 'Pending review';
$MESS['RETURN_STATUS_APPROVED']  = 'Approved';
// ...

In the personal account template:

$statusLabel = \Bitrix\Main\Localization\Loc::getMessage(
    'RETURN_STATUS_' . $returnStatusId
) ?: $returnStatusId;

Scope of work

  • Designing the status set for the specific business process
  • Installation script for creating/updating statuses
  • Allowed transition matrix with role-based restrictions
  • Transition validator via the OnBeforeSaleOrderReturnStatusChange event
  • Email notification templates for each "public" status
  • Localisation of labels for the personal account

Timeline: status configuration and transition matrix — 3–7 business days.