Configuring Product Return Management in 1C-Bitrix
Returns are a distinct business process that most 1C-Bitrix online stores handle poorly: the customer calls or writes in, a manager sorts out the order manually, and statuses are updated at best via comments. 1C-Bitrix (the sale module) has a built-in return system, but it requires configuration. Out of the box only basic functionality is provided: creating a return and refunding money through the payment system.
Return architecture in the sale module
Key entities:
-
\Bitrix\Sale\PaymentCollectionReturn— payment return -
\Bitrix\Sale\BasketItem— order line item being returned - Table
b_sale_order_return— return records - Table
b_sale_order_return_basket— line items within a return
Creating a return via API
namespace Local\Returns;
use Bitrix\Sale;
class ReturnManager
{
/**
* Create a return for an order
*
* @param int $orderId Order ID
* @param array $items [['basket_id' => int, 'quantity' => float, 'reason' => string], ...]
* @param string $returnType 'MONEY' | 'EXCHANGE' | 'CREDIT'
*/
public function createReturn(int $orderId, array $items, string $returnType = 'MONEY'): int
{
\Bitrix\Main\Loader::includeModule('sale');
$order = Sale\Order::load($orderId);
if (!$order) {
throw new \RuntimeException("Order #{$orderId} not found");
}
// Verify the order is paid
if (!$order->isPaid()) {
throw new \RuntimeException("Order #{$orderId} is not paid");
}
$returnCollection = $order->getPaymentCollection();
// Create the return object
$orderReturn = Sale\OrderReturn::create($order);
$orderReturn->setField('TYPE', $returnType);
$orderReturn->setField('REASON', 'Customer request');
// Add return line items
$basketCollection = $order->getBasket();
foreach ($items as $item) {
$basketItem = $basketCollection->getItemById($item['basket_id']);
if (!$basketItem) continue;
$maxQty = $basketItem->getQuantity();
$qty = min((float)$item['quantity'], $maxQty);
$returnItem = $orderReturn->getReturn()->createItem($basketItem);
$returnItem->setField('QUANTITY', $qty);
$returnItem->setField('REASON', $item['reason'] ?? '');
}
$result = $orderReturn->save();
if (!$result->isSuccess()) {
throw new \RuntimeException('Return creation failed: ' . implode('; ', $result->getErrorMessages()));
}
return $orderReturn->getId();
}
}
Configuring return statuses
In the admin panel: Online Store → Return Statuses. Recommended minimum set:
| Code | Name | Description |
|---|---|---|
| WAIT | Pending Review | New request, not yet processed |
| REVIEW | Under Review | Manager is examining the request |
| APPROVED | Approved | Return approved, awaiting the item |
| RECEIVED | Item Received | Warehouse accepted the returned item |
| REFUND | Refunded | Payment has been processed |
| REJECTED | Rejected | Return rejected with a reason |
| EXCHANGE | Exchange | Replacement with another item |
Creating a status via PHP:
\CSaleOrderReturnStatus::Add([
'ID' => 'WAIT',
'NAME' => 'Pending Review',
'SORT' => 100,
'COLOR' => '#f0ad4e',
]);
Integration with 1C for stock write-off and receipt
When a product is returned to the warehouse, stock levels must be updated. If stock is managed in 1C — when the return status changes to "Item Received", a notification is sent to 1C via a queue:
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderReturnStatusChange',
function (\Bitrix\Main\Event $event) {
$returnId = $event->getParameter('RETURN_ID');
$newStatus = $event->getParameter('NEW_STATUS_ID');
if ($newStatus === 'RECEIVED') {
\Local\OneC\StockSync::scheduleReturnSync($returnId);
}
if ($newStatus === 'REFUND') {
\Local\Returns\RefundProcessor::processPaymentReturn($returnId);
}
}
);
Refunding money through the payment system
Most Bitrix payment systems (YooKassa, Tinkoff, Sberbank) support API-based refunds. In Bitrix this is implemented through the payment system handler:
namespace Local\Returns;
class RefundProcessor
{
public static function processPaymentReturn(int $returnId): bool
{
\Bitrix\Main\Loader::includeModule('sale');
$return = \Bitrix\Sale\OrderReturn::loadById($returnId);
if (!$return) return false;
$order = \Bitrix\Sale\Order::load($return->getField('ORDER_ID'));
$payments = $order->getPaymentCollection();
$amount = $return->getField('REFUND_AMOUNT'); // amount to refund
foreach ($payments as $payment) {
if (!$payment->isPaid()) continue;
// The refund method depends on the payment system
$paySystem = $payment->getPaySystem();
if (!$paySystem) continue;
$result = $paySystem->refund($payment, $amount);
if ($result->isSuccess()) {
$return->setField('STATUS_ID', 'REFUND');
$return->setField('REFUND_DATE', new \Bitrix\Main\Type\DateTime());
$return->save();
return true;
}
}
return false;
}
}
Return access permissions
In Bitrix, permissions for working with returns are managed through roles in the sale module. Configuration: Online Store → Settings → Access Permissions.
Recommended roles:
- Returns Manager: view all returns, change statuses up to "Approved"
- Senior Manager: full permissions on returns, including refunds
- Customer: create a return request via the personal account
Personal account: return request component
The standard component bitrix:sale.order.return.edit allows customers to create a return request from their order history. Including it in the personal account template:
$APPLICATION->IncludeComponent(
'bitrix:sale.order.return.edit',
'default',
[
'ORDER_ID' => (int)$_GET['ORDER_ID'],
'RETURN_ID' => (int)$_GET['RETURN_ID'],
'SITE_ID' => SITE_ID,
'PATH_TO_RETURN_LIST' => '/personal/returns/',
]
);
Scope of work
- Configuring return statuses to match the business process
- Return request creation component in the personal account
- Status change event handlers (notifications, warehouse synchronisation)
- Refund integration via payment system APIs
- Admin interface: return list, filters, export
- Access permission configuration for staff roles
Timeline: basic setup with personal account and statuses — 1–2 weeks. Full system with automatic refunds and 1C integration — 3–5 weeks.







