Розробка системи RMA (Return Merchandise Authorization) на 1С-Бітрікс
Повернення без системи — хаос. Клієнт телефонує або пише на загальну пошту, менеджер вручну шукає замовлення, домовляється про повернення, надсилає інструкції. Відстежити статус повернення неможливо, аналітики немає. RMA-система автоматизує цей процес: клієнт створює заявку на повернення через особистий кабінет, менеджер обробляє її в адміністративній частині, система відстежує статуси. Бітрікс не має вбудованого RMA — його будують поверх модуля sale.
Юридичний контекст
Повернення товару регулюється відповідним законодавством про захист прав споживачів. Два основних підстави:
- Неналежна якість — дефект, брак. Строк пред'явлення — у межах гарантійного терміну.
- Належна якість — «передумав». Лише для непродовольчих товарів, 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
$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 днів |







