How to programmatically delete an order in 1C-Bitrix on D7: complete guide and common mistakes

26.09.20255 min read
Makar Kucherenko
Python developerMakar Kucherenko

Working with orders in online stores using 1C-Bitrix often brings surprises to novice developers. One of the most common tasks is to write a script to bulk clean up old or test orders.

It would seem that it is enough to call one method. On the old kernel this was done via CSaleOrder::Delete($id). On the new D7 core, an attempt to execute Sale\Order::delete($id) often ends with an Exception and an error "The order has reserves" or "Order paid".

It's a matter of strict architecture: an order in D7 is a container. To remove a container, you must first correctly disassemble its contents.


Why can’t an order be deleted directly?

In D7 the essence of the Order (Bitrix\Sale\Order) is inextricably linked with Collections:

  1. Collection of payments (PaymentCollection) - contains transactions.
  2. Collection of shipments (ShipmentCollection) - is responsible for logistics and reserving balances in warehouses.
  3. Cart (Basket) — goods inside the order.

If at least one payment has been made (status “Paid”) or the item has been reserved/shipped, the kernel will not allow the top-level object to be deleted. This is protection against violation of financial and warehouse consistency.

The secure removal algorithm consists of strictly sequential steps:

  1. Upload order object.
  2. Go through all payments and mark them as “returned” (cancel payment).
  3. Go through all shipments, remove the flag DEDUCTED (shipped) and cancel reserves.
  4. Save changes to collections.
  5. Call the method for deleting the order itself.

Working code example on D7

Below is a universal script for deleting an array of orders. It can be invoked through agents, cron scripts, or the CLI.

use Bitrix\Main\Loader;
use Bitrix\Sale;

// Проверяем подключение модуля интернет-магазина
if (!Loader::includeModule('sale')) {
    die("Модуль sale не установлен");
}

// Массив ID заказов, которые нужно удалить
$orderIds = [1001, 1002, 1003]; 
$logFile = $_SERVER["DOCUMENT_ROOT"] . "/local/logs/order_delete.log";

foreach ($orderIds as $orderId) {
    // 1. Загружаем объект заказа
    $order = Sale\Order::load($orderId);
    
    if (!$order) {
        file_put_contents($logFile, "[$orderId] Заказ не найден\n", FILE_APPEND);
        continue;
    }

    try {
        // 2. Отменяем оплаты
        $paymentCollection = $order->getPaymentCollection();
        foreach ($paymentCollection as $payment) {
            if ($payment->isPaid()) {
                // Ставим флаг возврата
                $payment->setReturn("Y"); 
            }
        }

        // 3. Отменяем отгрузки и системную отгрузку (System shipment)
        $shipmentCollection = $order->getShipmentCollection();
        foreach ($shipmentCollection as $shipment) {
            if (!$shipment->isSystem()) {
                if ($shipment->isShipped()) {
                    // Снимаем флаг отгрузки (возвращает остатки)
                    $shipment->setField("DEDUCTED", "N"); 
                }
                // На всякий случай снимаем резервирование
                $shipment->setField("RESERVED", "N");
            }
        }

        // 4. Обязательно сохраняем сброшенные статусы до удаления!
        $saveResult = $order->save();
        if (!$saveResult->isSuccess()) {
            throw new \Exception(implode(", ", $saveResult->getErrorMessages()));
        }

        // 5. Удаляем сам заказ
        $deleteResult = Sale\Order::delete($orderId);
        
        if ($deleteResult->isSuccess()) {
            file_put_contents($logFile, "[$orderId] Успешно удален\n", FILE_APPEND);
        } else {
            throw new \Exception(implode(", ", $deleteResult->getErrorMessages()));
        }

    } catch (\Exception $e) {
        file_put_contents($logFile, "[$orderId] Ошибка: " . $e->getMessage() . "\n", FILE_APPEND);
    }
}

Alternative: Cancel rather than delete

In 90% of cases, online stores no need to physically delete orders at all. Deletion disrupts continuous numbering, spoils conversion statistics and interferes with analytics in dashboards.

The correct business scenario is order cancellation.

$order = \Bitrix\Sale\Order::load($orderId);
$order->setField('CANCELED', 'Y');
$order->setField('REASON_CANCELED', 'Тестовый заказ / Ошибка клиента');
$order->save();

When changing status CANCELED on Y, Bitrix automatically will return reserved goods to the warehouse if this is configured in the parameters of the Online Store module. It is better to leave physical deletion only for cleaning the database by developers after the test operation stage.


Summary

Working with collections is a fundamental principle of D7. Remember the main rule: never change table data directly through SQL queries like DELETE FROM b_sale_order. The Bitrix database is deeply relational, and a direct SQL query will leave behind broken indexes and garbage in the bucket tables (b_sale_basket) and discrepancies in warehouse balances (b_catalog_store_product).

Need help with support and improvement of the online store on 1C-Bitrix? NBM-IT developers write clean code in D7, integrate complex APIs and optimize highload projects. Leave a request for a technical audit.

Free SEO audit of your website

Leave a request and our specialists will find areas of search traffic growth.

Leave your contacts - we will call you back, sort out the problem and offer the best way. We have more than 350 projects behind us, each of which we launched with an individual approach. We guarantee expert advice during business hours.