Разработка системы RMA (Return Merchandise Authorization) на 1С-Битрикс

Наша компания занимается разработкой, поддержкой и обслуживанием решений на Битрикс и Битрикс24 любой сложности. От простых одностраничных сайтов до сложных интернет магазинов, CRM систем с интеграцией 1С и телефонии. Опыт разработчиков подтвержден сертификатами от вендора.
Предлагаемые услуги
Показано 1 из 1 услугВсе 1626 услуг
Разработка системы RMA (Return Merchandise Authorization) на 1С-Битрикс
Средняя
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1181
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    813
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Разработка на базе Битрикс, Битрикс24, 1С для компании Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Разработка на базе 1С Предприятие для компании МИРСАНБЕЛ
    747
  • image_crm_dolbimby_434_0.webp
    Разработка сайта на CRM Битрикс24 для компании DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Разработка на базе Битрикс24 для компании ТЕХНОТОРГКОМПЛЕКС
    976

Разработка системы RMA (Return Merchandise Authorization) на 1С-Битрикс

Возвраты без системы — хаос. Клиент звонит или пишет на общую почту, менеджер вручную ищет заказ, договаривается о возврате, отправляет инструкции. Отследить статус возврата невозможно, аналитики нет. RMA-система автоматизирует этот процесс: клиент создаёт заявку на возврат через личный кабинет, менеджер обрабатывает её в административной части, система отслеживает статусы. Битрикс не имеет встроенного RMA — его строят поверх модуля sale.

Юридический контекст

Возврат товара регулируется статьями 18, 25 Закона о защите прав потребителей (ЗоЗПП). Два основных основания:

  • Ненадлежащее качество (ст. 18) — дефект, брак. Срок предъявления — в пределах гарантийного срока.
  • Надлежащее качество (ст. 25) — «передумал». Только для непродовольственных товаров, 14 дней, товар не должен быть в употреблении.

В системе RMA оба основания должны быть представлены отдельными типами заявок с разными полями и процессами.

Модель данных

Таблица RMA-заявок:

class RmaRequestTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'b_local_rma_request'; }

    public static function getMap(): array
    {
        return [
            new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
            new StringField('NUMBER'),              // RMA-YYYYMMDD-XXXX
            new IntegerField('ORDER_ID'),           // b_sale_order
            new IntegerField('USER_ID'),
            new EnumField('TYPE', ['values' => ['DEFECT', 'EXCHANGE', 'REFUND']]),
            new EnumField('STATUS', ['values' => ['NEW', 'UNDER_REVIEW', 'APPROVED', 'REJECTED', 'RETURNED', 'REFUNDED', 'CLOSED']]),
            new TextField('DESCRIPTION'),           // Описание причины от клиента
            new StringField('DEFECT_PHOTO_IDS'),    // JSON-массив b_file ID
            new StringField('REJECT_REASON'),       // Причина отказа (если REJECTED)
            new FloatField('REFUND_AMOUNT'),        // Сумма к возврату
            new IntegerField('RESPONSIBLE_ID'),     // Ответственный менеджер
            new DatetimeField('CREATED_AT'),
            new DatetimeField('UPDATED_AT'),
            new DatetimeField('DEADLINE_AT'),       // Дедлайн обработки (14 дней)
        ];
    }
}

Позиции возврата:

class RmaItemTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'b_local_rma_item'; }

    public static function getMap(): array
    {
        return [
            new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
            new IntegerField('RMA_ID'),
            new IntegerField('ORDER_BASKET_ID'),    // b_sale_basket
            new IntegerField('PRODUCT_ID'),
            new StringField('PRODUCT_NAME'),
            new FloatField('QUANTITY'),
            new FloatField('PRICE'),
            new StringField('REASON_CODE'),         // DEFECTIVE, NOT_AS_DESCRIBED, CHANGED_MIND, WRONG_ITEM
            new StringField('CONDITION'),           // NEW, USED, DAMAGED
        ];
    }
}

История статусов:

class RmaHistoryTable extends \Bitrix\Main\ORM\Data\DataManager
{
    public static function getTableName(): string { return 'b_local_rma_history'; }
    // RMA_ID, OLD_STATUS, NEW_STATUS, USER_ID, COMMENT, CREATED_AT
}

Генерация номера RMA

class RmaNumberGenerator
{
    public static function generate(): string
    {
        $date   = date('Ymd');
        $lastId = RmaRequestTable::getList([
            'order'  => ['ID' => 'DESC'],
            'limit'  => 1,
            'select' => ['ID'],
        ])->fetch()['ID'] ?? 0;

        return 'RMA-' . $date . '-' . str_pad($lastId + 1, 4, '0', STR_PAD_LEFT);
    }
}

Создание заявки из личного кабинета

Страница /personal/rma/create/?order_id=12345:

class RmaCreateComponent extends \CBitrixComponent
{
    public function executeComponent(): void
    {
        $orderId = (int)$this->arParams['ORDER_ID'];

        if (!$orderId || !$this->isOrderOwner($orderId)) {
            LocalRedirect('/personal/orders/');
            return;
        }

        // Загрузить товары заказа
        $order  = \Bitrix\Sale\Order::load($orderId);
        $basket = $order->getBasket();

        $items = [];
        foreach ($basket as $item) {
            // Проверить: не истёк ли срок для возврата
            $daysSinceOrder = (new \DateTime())->diff(new \DateTime($order->getField('DATE_INSERT')))->days;
            if ($daysSinceOrder > 365) continue; // Пропустить товары старше года

            $items[] = [
                'BASKET_ID'    => $item->getId(),
                'PRODUCT_ID'   => $item->getProductId(),
                'NAME'         => $item->getField('NAME'),
                'QUANTITY'     => $item->getQuantity(),
                'PRICE'        => $item->getPrice(),
                'CAN_REFUND'   => $daysSinceOrder <= 14, // 14 дней для возврата без дефекта
            ];
        }

        if ($this->request->isPost() && check_bitrix_sessid()) {
            $this->createRmaRequest($orderId, $items);
        }

        $this->arResult['ORDER'] = $order;
        $this->arResult['ITEMS'] = $items;
        $this->includeComponentTemplate();
    }

    private function createRmaRequest(int $orderId, array $items): void
    {
        $selectedItems = [];
        foreach ($this->request->getPost('items') as $basketId => $itemData) {
            if (empty($itemData['selected'])) continue;
            $selectedItems[] = [
                'ORDER_BASKET_ID' => $basketId,
                'QUANTITY'        => (float)$itemData['quantity'],
                'REASON_CODE'     => $itemData['reason'],
                'CONDITION'       => $itemData['condition'],
            ];
        }

        if (empty($selectedItems)) {
            $this->arResult['ERROR'] = 'Выберите хотя бы один товар для возврата';
            return;
        }

        // Загрузить фотографии дефектов
        $photoIds = [];
        foreach ($_FILES['defect_photos']['tmp_name'] as $i => $tmpName) {
            if (is_uploaded_file($tmpName)) {
                $fileId = \CFile::SaveFile([
                    'name'     => $_FILES['defect_photos']['name'][$i],
                    'size'     => $_FILES['defect_photos']['size'][$i],
                    'tmp_name' => $tmpName,
                    'type'     => $_FILES['defect_photos']['type'][$i],
                ], 'rma');
                if ($fileId) $photoIds[] = $fileId;
            }
        }

        $addResult = RmaRequestTable::add([
            'NUMBER'         => RmaNumberGenerator::generate(),
            'ORDER_ID'       => $orderId,
            'USER_ID'        => $GLOBALS['USER']->GetID(),
            'TYPE'           => $this->request->getPost('type'),
            'STATUS'         => 'NEW',
            'DESCRIPTION'    => htmlspecialchars($this->request->getPost('description')),
            'DEFECT_PHOTO_IDS' => json_encode($photoIds),
            'CREATED_AT'     => new \Bitrix\Main\Type\DateTime(),
            'DEADLINE_AT'    => \Bitrix\Main\Type\DateTime::createFromPhp(new \DateTime('+14 days')),
        ]);

        $rmaId = $addResult->getId();
        foreach ($selectedItems as $item) {
            RmaItemTable::add(array_merge($item, ['RMA_ID' => $rmaId]));
        }

        // Уведомить менеджера
        $this->notifyManager($rmaId);

        $this->arResult['SUCCESS'] = true;
        $this->arResult['RMA_NUMBER'] = 'RMA-...' ;
    }
}

Административная обработка

Страница /local/admin/rma_list.php — список всех RMA с фильтрами по статусу, дедлайну, ответственному. Действия менеджера:

// Одобрить возврат
public function approve(int $rmaId, float $refundAmount, int $managerId): void
{
    RmaRequestTable::update($rmaId, [
        'STATUS'         => 'APPROVED',
        'REFUND_AMOUNT'  => $refundAmount,
        'RESPONSIBLE_ID' => $managerId,
        'UPDATED_AT'     => new \Bitrix\Main\Type\DateTime(),
    ]);

    RmaHistoryTable::add([
        'RMA_ID'     => $rmaId,
        'OLD_STATUS' => 'UNDER_REVIEW',
        'NEW_STATUS' => 'APPROVED',
        'USER_ID'    => $managerId,
        'COMMENT'    => 'Заявка одобрена',
    ]);

    // Инициировать возврат средств через платёжную систему
    $this->initiateRefund($rmaId, $refundAmount);

    // Уведомить клиента
    $this->notifyClient($rmaId, 'APPROVED');
}

Интеграция с возвратом платежа

Для возврата денег через платёжную систему — используем API того же шлюза, через который принята оплата. Большинство российских шлюзов (ЮKassa, CloudPayments, СБПЭЙ) имеют метод рефанда:

// Пример для ЮKassa
$client = new \YooKassa\Client();
$client->setAuth($shopId, $secretKey);

$originalPaymentId = $this->getOriginalPaymentId($orderId); // из b_sale_pay_system_action
$refund = $client->createRefund([
    'payment_id' => $originalPaymentId,
    'amount'     => ['value' => $refundAmount, 'currency' => 'RUB'],
    'description' => 'Возврат по RMA #' . $rmaNumber,
]);

После успешного рефанда — статус RMA → REFUNDED, пополнить баланс склада (если товар возвращён на склад).

Сроки разработки

Вариант Состав Срок
Базовая RMA Форма заявки, список в ЛК, статусы 8–12 дней
С обработкой в админке Интерфейс менеджера, история, уведомления 12–18 дней
Полная система + Возврат платежей, интеграция со складом 18–28 дней