Setting up product return management 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
    1173
  • 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
    745
  • 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 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.