Перейти к содержимому

Заказ из кода не сохраняется - разбор причин

Скрипт создаёт заказ, отрабатывает до конца, а заказа в списке нет. Разбираем причины по убыванию частоты, начиная с результата сохранения.

С чего начать

Читаем результат сохранения, а не факт выполнения:

$result = $order->save();
if (!$result->isSuccess()) {
foreach ($result->getErrorMessages() as $message) {
echo $message, "\n"; // здесь лежит настоящая причина отказа
}
}
// сохранение возвращает объект результата и исключений не бросает
// $result->getId() отдаёт номер записи только при успешном сохранении

Сохранение заказа не бросает исключение при отказе, а возвращает объект с ошибками. Код без этой проверки выглядит успешным при любом исходе, и разбор всегда начинают именно отсюда.

Проверяем обязательный набор перед сохранением:

printf("тип плательщика=%s позиций=%d сайт=%s\n",
$order->getPersonTypeId(),
$order->getBasket() ? $order->getBasket()->count() : 0,
$order->getSiteId());
// заказ без типа плательщика и без позиций корзины не сохранится
// сайт заказа определяет, в каком списке он потом появится

Тип плательщика, сайт и непустая корзина - минимум для сохранения. Заказ, собранный из кода без одного из этих трёх, отклоняется на самом сохранении, а причина остаётся в объекте результата.

Смотрим, не отменил ли заказ чужой обработчик:

$handlers = \Bitrix\Main\EventManager::getInstance()
->findEventHandlers('sale', 'OnSaleOrderBeforeSaved');
printf("обработчиков события: %d\n", count($handlers));
// свой или сторонний обработчик умеет отменить сохранение заказа целиком

Событие перед сохранением умеет отменять операцию. Решение из маркетплейса и свой код проверки заказа отменяют сохранение одинаково тихо, а сообщение попадает в тот же объект результата.

Собираем минимальный заказ целиком:

$order = \Bitrix\Sale\Order::create(SITE_ID, $userId);
$order->setPersonTypeId($personTypeId); // без него заказ не сохранится
$order->setBasket($basket); // корзина с позициями
$shipment = $order->getShipmentCollection()->createItem();
$shipment->setFields(['DELIVERY_ID' => $deliveryId]);
$payment = $order->getPaymentCollection()->createItem();
$payment->setFields(['PAY_SYSTEM_ID' => $paySystemId, 'SUM' => $order->getPrice()]);

Заказ собирают целиком и сохраняют один раз. Сохранение записывает заказ, корзину, отгрузки и платежи каскадом, поэтому промежуточные сохранения только добавляют события и лишние запросы.

Причины

  1. Результат сохранения не проверяется примерно 30% случаев

    ПризнакСкрипт отрабатывает без ошибок, заказа в списке нет, в журнале пусто.

    ПроверкаПечатаем сообщения об ошибках из объекта результата сразу после сохранения.

    Что делатьПроверяем результат всегда: сохранение сообщает об отказе объектом, а не исключением.

  2. Не задан тип плательщика примерно 25% случаев

    ПризнакВ ошибках упоминается тип плательщика или свойства заказа, которых нет.

    ПроверкаСмотрим, задан ли у заказа тип плательщика и существует ли он в настройках магазина.

    Что делатьЗадаём тип плательщика до сохранения: от него зависят свойства заказа и доступные службы.

  3. Корзина заказа пуста или не привязана примерно 20% случаев

    ПризнакЗаказ создан объектом, но позиций в нём ноль, сумма нулевая.

    ПроверкаСчитаем позиции корзины перед сохранением и проверяем, привязана ли корзина к заказу.

    Что делатьНаполняем корзину и привязываем её к заказу: заказ без позиций магазину не нужен.

  4. Не заданы отгрузка и оплата примерно 15% случаев

    ПризнакЗаказ сохраняется, но выглядит незавершённым: нет доставки, нет платежа, суммы не сходятся.

    ПроверкаСмотрим коллекции отгрузок и платежей заказа и наличие несистемных записей в них.

    Что делатьДобавляем отгрузку со службой доставки и платёж с платёжной системой до сохранения.

  5. Сохранение отменил обработчик события примерно 10% случаев

    ПризнакИз административной части заказ создаётся, из своего кода - нет.

    ПроверкаСмотрим список обработчиков события перед сохранением заказа и временно отключаем свои.

    Что делатьПравим условие проверки в обработчике: оно не должно срабатывать на корректном заказе.

Частые вопросы

Почему сохранение не бросает исключение?

Ядро продаж возвращает объект результата: так ошибку можно показать покупателю, а не уронить страницу. Проверка результата в этом подходе обязательна.

Нужно ли сохранять заказ несколько раз?

Нет, одно сохранение записывает заказ, корзину, отгрузки и платежи каскадом. Несколько сохранений подряд только добавляют лишние события и запросы.

Почему в коллекции отгрузок есть лишняя запись?

Это системная отгрузка: она хранит нераспределённые товары и существует всегда. При переборе её пропускают, иначе действия применяются не к той записи.

Как менять уже сохранённый заказ?

Специализированными методами: смена статуса, разрешение доставки, запись свойств. Общая запись полей мимо них не поднимает нужные события и ломает связанные данные.

Заказ создан, но покупатель его не видит.

Заказ привязан к другому пользователю или к другому сайту. Оба значения задаются при создании и потом молча определяют видимость заказа в личном кабинете.

Смежное

Первоисточник