Обмен, импорт и экспорт заказов
Обмен заказами связывает объекты модуля sale с учетной, логистической или другой внешней системой. Интеграция выполняет три задачи:
-
получает заказ вместе с корзиной, свойствами, оплатами и отгрузками,
-
сопоставляет внешние и внутренние идентификаторы,
-
применяет входящие изменения через API заказа.
Модуль содержит штатный механизм обмена с 1С и классы пространства имен Bitrix\Sale\Exchange. Для интеграции с произвольной системой можно использовать объектную модель заказа и собственный клиент внешней системы. Выбор зависит от формата данных и протокола внешней системы.
Выбрать сценарий обмена
Импорт и экспорт обозначают направление данных относительно интернет-магазина.
|
Сценарий |
Направление |
Основная задача |
Подходящий API |
|
Экспорт заказа |
Из интернет-магазина во внешнюю систему |
Прочитать данные заказа и преобразовать их во внешний формат |
Класс |
|
Импорт заказа или изменений |
Из внешней системы в интернет-магазин |
Найти заказ по идентификатору, проверить данные и применить изменения |
Штатный менеджер импорта для поддерживаемого формата. Класс |
|
Двусторонняя синхронизация |
В обоих направлениях |
Сопоставить идентификаторы, статусы и версии данных, исключить повторную обработку |
Механизм обмена и журнал интеграции |
|
Передача заказа в произвольный сервис |
Из интернет-магазина во внешний HTTP API или очередь |
Определить формат передаваемых данных и обработать ответ сервиса |
Класс |
Используйте штатный обмен с 1С для систем, которые поддерживают его формат. Для системы с другим форматом создайте собственный клиент внешнего API. Внутренние классы протокола не заменяют такой клиент.
Выбрать способ интеграции
Для обмена с 1С используйте штатные настройки и обработчики поддерживаемого протокола. Компонент bitrix:sale.export.1c применяет CSaleExport при формировании выгрузки и CSaleOrderLoader при загрузке входящих данных. Не копируйте внутреннюю реализацию обработчика в собственный модуль.
Для произвольной интеграции создайте отдельный слой:
-
Клиент или обработчик внешней системы получает сообщение.
-
Валидатор проверяет формат и состав входящих данных.
-
Слой сопоставления преобразует внешние идентификаторы и состояния во внутренние.
-
Сервис интеграции загружает и изменяет
Bitrix\Sale\Order. -
Журнал интеграции фиксирует попытку, результат и возможность повторного запуска.
При таком разделении код интеграции не зависит от внутреннего формата штатного обмена. Получение данных, сопоставление и изменение заказа можно тестировать отдельно.
Подготовить данные и выбрать API
До запуска обмена подготовьте:
-
настройки штатного обмена или отдельные учетные данные внешнего сервиса,
-
таблицы сопоставления заказов, товаров, статусов, оплат, отгрузок и свойств,
-
свойства заказа для всех типов плательщика, с которыми работает интеграция,
-
журнал сообщений с уникальным идентификатором операции,
-
очередь повторных попыток и правила ручной обработки конфликтов,
-
технического пользователя или другой способ аутентификации входящего запроса.
Проверьте права в точке входа интеграции. Объектная модель заказа не определяет, имеет ли вызывающий пользователь право импортировать данные. Для фонового процесса ограничьте доступ к обработчику, а для операции из пользовательского запроса примените проверки прав проекта до загрузки и изменения заказа.
Определить данные заказа в обмене
Перед передачей данных определите формат и состав данных для обмена. Укажите обязательные поля, внешний идентификатор каждого объекта, формат дат и сумм, правила для пустых значений и допустимые переходы состояния.
|
Данные |
Объект модуля |
Что согласовать с внешней системой |
|
Номер, дата, покупатель, статус, сумма и валюта |
|
Отличие внутреннего |
|
Товары, количество, цена, скидка и НДС |
|
Идентификатор товара или предложения, единица измерения и правило пересчета цены |
|
Контактные данные и реквизиты |
|
Символьный код свойства, тип значения и правило обработки отсутствующего свойства |
|
Оплаты |
|
Идентификатор платежа, платежная система, сумма и признак оплаты |
|
Отгрузки |
|
Идентификатор отгрузки, служба доставки, состав, трек-номер и признак отгрузки |
|
Состояние синхронизации |
Данные интеграции |
Внешний идентификатор, версия сообщения, дата попытки и результат обработки |
Храните отдельное сопоставление внутреннего и внешнего идентификаторов в данных интеграции. Не используйте номер заказа как единственный ключ синхронизации, если внешняя система может изменить или повторно использовать номер.
Выбрать API механизма обмена
Пространство имен Bitrix\Sale\Exchange содержит инфраструктуру штатного импорта и экспорта. Перед реализацией выберите API по задаче интеграции.
|
API |
Роль |
|
|
Регистрирует настройки, правила разрешения коллизий и критерии поиска для типов импортируемых объектов |
|
|
Регистрирует настройки типов экспортируемых объектов и определяет направление и режим штатного обмена |
|
|
Внутренний базовый класс для импорта заказа, оплат, отгрузок и профиля покупателя |
|
|
Загружает и изменяет заказ вместе со связанными коллекциями |
|
|
Возвращает активные торговые платформы и объект платформы по идентификатору |
|
|
Базовый класс обработчика торговой платформы |
Классы ManagerImport и ManagerExport обслуживают внутреннюю конфигурацию штатных пакетов обмена. Метод registerInstance() обоих классов помечен как внутренний. Не собирайте собственный импорт или экспорт прямыми вызовами этих менеджеров. Используйте штатную точку обмена с 1С или объектную модель заказа для собственного протокола.
Импорт 1С записывает в объекты обмена поля ID_1C и VERSION_1C, а изменения помечает полем UPDATED_1C. У заказа также есть признак EXTERNAL_ORDER. Эти поля относятся к штатному механизму. Для произвольного протокола храните отдельную таблицу сопоставления, если внешний идентификатор не является идентификатором 1С.
Как запускается штатный обмен с 1С
Служебная точка /bitrix/admin/1c_exchange.php?type=sale подключает компонент bitrix:sale.export.1c. Компонент проверяет авторизацию и права группы, принимает файлы обмена, запускает импорт и формирует ответ протокола. Внутри компонента используются CSaleExport, CSaleOrderLoader и классы Bitrix\Sale\Exchange.
Работа компонента зависит от настроек модуля.
|
Настройка |
Значение и роль |
Значение по умолчанию |
|
|
Строковый идентификатор сайта ограничивает заказы, которые участвуют в обмене |
Пустая строка — без фильтра по сайту |
|
|
|
Пустая строка — фильтр выключен |
|
|
|
Пустая строка — фильтр выключен |
|
|
Строковый код статуса задает начальный статус отбора. Механизм включает его и следующие статусы по сортировке |
Пустая строка — фильтр выключен |
|
|
|
Пустая строка — изменение выключено |
|
|
|
|
|
|
Строка с идентификаторами групп через запятую задает доступ к точке обмена |
|
Настраивайте эти параметры в интерфейсе модуля. Не вызывайте компонент или служебную точку из PHP-кода проекта для запуска произвольной интеграции.
Классы CSaleExport, CSaleOrderLoader и связанные обработчики классического API остаются частью отдельных штатных сценариев и совместимости существующих проектов. Не выбирайте эти классы как основной API для новой произвольной интеграции. Для чтения и изменения заказа используйте объектную модель Bitrix\Sale\Order, если конкретный штатный обработчик не требует использования классического API.
Реализовать экспорт и импорт
Для произвольной интеграции используйте объектную модель заказа. При экспорте соберите данные без изменения заказа. При импорте проверьте входящие значения и сохраните изменения одной операцией.
Подготовить заказ к экспорту
Экспорт должен читать сохраненное состояние заказа и не менять его. Загрузите заказ непосредственно перед формированием сообщения, соберите только согласованные поля и преобразуйте объекты в массив данных.
use Bitrix\Main\Loader;
use Bitrix\Sale\Order;
if (!Loader::includeModule('sale'))
{
throw new \RuntimeException('Не удалось подключить модуль sale');
}
$orderId = 123;
$order = Order::load($orderId);
if (!$order)
{
throw new \RuntimeException('Заказ не найден');
}
$exchangeData = [
'ORDER' => [
'ID' => $order->getId(),
'ACCOUNT_NUMBER' => (string) $order->getField('ACCOUNT_NUMBER'),
'STATUS_ID' => (string) $order->getField('STATUS_ID'),
'PRICE' => $order->getPrice(),
'CURRENCY' => $order->getCurrency(),
],
'ITEMS' => [],
'PROPERTIES' => [],
'PAYMENTS' => [],
'SHIPMENTS' => [],
];
foreach ($order->getBasket() as $basketItem)
{
$exchangeData['ITEMS'][] = [
'ID' => $basketItem->getId(),
'PRODUCT_ID' => (int) $basketItem->getField('PRODUCT_ID'),
'NAME' => (string) $basketItem->getField('NAME'),
'QUANTITY' => $basketItem->getQuantity(),
'PRICE' => $basketItem->getPrice(),
'CURRENCY' => (string) $basketItem->getField('CURRENCY'),
];
}
foreach ($order->getPropertyCollection() as $propertyValue)
{
$property = $propertyValue->getProperty();
$exchangeData['PROPERTIES'][] = [
'CODE' => (string) ($property['CODE'] ?? ''),
'VALUE' => $propertyValue->getValue(),
];
}
foreach ($order->getPaymentCollection() as $payment)
{
$exchangeData['PAYMENTS'][] = [
'ID' => $payment->getId(),
'PAY_SYSTEM_ID' => $payment->getPaymentSystemId(),
'SUM' => $payment->getSum(),
'PAID' => $payment->isPaid(),
];
}
foreach ($order->getShipmentCollection() as $shipment)
{
if ($shipment->isSystem())
{
continue;
}
$exchangeData['SHIPMENTS'][] = [
'ID' => $shipment->getId(),
'DELIVERY_ID' => $shipment->getDeliveryId(),
'PRICE' => $shipment->getPrice(),
'SHIPPED' => $shipment->isShipped(),
'TRACKING_NUMBER' => (string) $shipment->getField(
'TRACKING_NUMBER'
),
];
}
Массив $exchangeData не является форматом штатного обмена с 1С. Это пример формата данных для произвольной интеграции. Передайте массив в сериализатор, HTTP-клиент или очередь проекта и отдельно обработайте результат отправки.
Не вызывайте Order::save() в коде экспорта. Если нужно отметить успешную передачу, сохраните состояние синхронизации в отдельном журнале интеграции или измените заказ в отдельной операции с явной проверкой результата.
Обработать входящее изменение заказа
Разделите импорт на этапы: проверку сообщения, поиск заказа, сопоставление значений, изменение объектов и фиксацию результата. Не передавайте внешние данные напрямую в поля заказа.
-
Проверьте источник сообщения и право интеграции выполнять операцию.
-
Проверьте идентификатор сообщения, формат и обязательные поля.
-
Найдите сопоставленный заказ и убедитесь, что сообщение еще не обработано.
-
Сопоставьте с внутренними значениями внешние статусы, идентификаторы оплат и отгрузок, а также значения свойств.
-
Измените загруженный объект заказа и связанные коллекции.
-
Сохраните заказ через
Order::save()и обработайте ошибки. -
Только после успешного сохранения отметьте сообщение обработанным.
Этот сценарий обновляет существующий заказ. Если внешняя система должна создавать новые заказы, сначала согласуйте обязательные настройки корзины, сайта, покупателя и типа плательщика, а также оплат и отгрузок, если импорт создает эти объекты. После сохранения нового заказа запишите соответствие его внутреннего и внешнего идентификаторов. Полный порядок создания заказа — в статье Создание заказа.
В примере используются уже проверенные внутренний код статуса и значение свойства. Поиск свойства по коду не создает новое свойство заказа.
use Bitrix\Main\Loader;
use Bitrix\Sale\Order;
if (!Loader::includeModule('sale'))
{
throw new \RuntimeException('Не удалось подключить модуль sale');
}
// Внутренний идентификатор заказа из таблицы сопоставления
$orderId = 123;
// Внутренний код статуса после сопоставления
$statusId = 'P';
// Значение свойства после проверки формата
$externalComment = 'Изменено учетной системой';
$order = Order::load($orderId);
if (!$order)
{
throw new \RuntimeException('Заказ не найден');
}
$statusResult = $order->setField('STATUS_ID', $statusId);
if (!$statusResult->isSuccess())
{
throw new \RuntimeException(
implode('; ', $statusResult->getErrorMessages())
);
}
$property = $order
->getPropertyCollection()
->getItemByOrderPropertyCode('EXTERNAL_COMMENT')
;
if (!$property)
{
throw new \RuntimeException(
'Свойство заказа EXTERNAL_COMMENT не найдено'
);
}
$propertyResult = $property->setValue($externalComment);
if (!$propertyResult->isSuccess())
{
throw new \RuntimeException(
implode('; ', $propertyResult->getErrorMessages())
);
}
$saveResult = $order->save();
if (!$saveResult->isSuccess())
{
throw new \RuntimeException(
implode('; ', $saveResult->getErrorMessages())
);
}
Код статуса P и свойство EXTERNAL_COMMENT условные. Создайте их в настройках проекта или замените фактическими значениями. Если входящие данные меняют корзину, стоимость доставки, скидки или налоги, пересчитайте заказ до сохранения. Порядок изменений и пересчета описан в статье Изменение и чтение заказа.
Сопоставить состояния и связанные объекты
Внешний статус не должен автоматически становиться кодом статуса заказа. Храните явные таблицы сопоставления и отклоняйте неизвестные значения.
|
Внешние данные |
Внутренний объект |
Правило сопоставления |
|
Состояние заказа |
|
Разрешайте только переходы, которые поддерживает процесс магазина |
|
Состояние оплаты |
Объект |
Найдите конкретную оплату по сохраненному соответствию и изменяйте ее через объектную модель |
|
Состояние доставки |
Объект |
Не смешивайте статус отгрузки, разрешение доставки и трек-номер |
|
Товар |
Позиция |
Сопоставляйте внешний код с товаром или торговым предложением и проверяйте единицу измерения |
|
Реквизит покупателя |
Значение свойства заказа |
Сопоставляйте по стабильному символьному коду, а не по отображаемому названию |
Одна внешняя операция может изменить несколько частей заказа. Применяйте связанные изменения к одному загруженному объекту и вызывайте Order::save() один раз после проверок и необходимых расчетов.
Не подтверждайте оплату только по общему статусу внешнего заказа. Для финансовой операции нужны идентификатор конкретного платежа, сумма, валюта и подтвержденный ответ платежной или учетной системы.
Сохранить свойства заказа при импорте
Коллекция свойств содержит только свойства, доступные заказу с его типом плательщика. Импорт должен обновлять выбранные значения и не заменять всю коллекцию входным массивом.
Для каждого входящего свойства:
-
Найдите внутреннее свойство по заранее настроенному символьному коду.
-
Проверьте тип и допустимый формат значения.
-
Измените найденный объект значения свойства.
-
Оставьте остальные свойства без изменений.
Если свойство не найдено, не создавайте его автоматически в процессе импорта. Зафиксируйте ошибку сопоставления и настройте свойство для нужного типа плательщика.
Как обеспечить надежность обмена
Надежный обмен должен корректно обрабатывать повторные сообщения, одновременные изменения и ошибки. Настройте повторный запуск, разрешение конфликтов и журналирование.
Обеспечить повторный запуск
Внешняя система может повторно отправить сообщение после тайм-аута или из-за того, что не получила ответ интернет-магазина. Обрабатывайте повторное сообщение без повторного изменения оплаты, количества товара или статуса.
Сохраните в журнале интеграции:
-
идентификатор источника и сообщения,
-
внутренний и внешний идентификаторы заказа,
-
тип операции и версию данных,
-
время получения и обработки,
-
результат и текст ошибки,
-
контрольную сумму значимых данных, если внешний протокол не передает версию.
Перед изменением заказа проверьте уникальность сообщения. Записывайте успешный результат после Order::save(). Если внешний протокол поддерживает подтверждение, отправляйте его после фиксации результата.
Обработать конкурирующие изменения
Импорт может выполняться одновременно с редактированием заказа менеджером или другим интеграционным процессом. До изменения сравните версию внешних данных с последним обработанным состоянием. Заранее определите владельца каждого поля. Укажите, какая система может менять значение и чьи данные имеют приоритет при конфликте.
Сначала выполните HTTP-запрос к внешней системе и проверьте сообщение. Затем измените заказ локально. Если изменение выполняется в транзакции базы данных, завершите ее сразу после попытки сохранить заказ. При успешном сохранении зафиксируйте изменения, при ошибке — откатите. Для очереди повторно загрузите заказ непосредственно перед изменением.
Если конфликт нельзя разрешить автоматически, не перезаписывайте локальные данные. Зафиксируйте конфликт в журнале и передайте его на ручную обработку.
Изменение и сохранение заказа может запускать стандартные события и другие обработчики проекта. Проверяйте источник операции перед обратным экспортом, иначе входящий импорт может запустить рекурсивный обмен. Сохраняйте признак источника вместе с данными операции. Обработчик экспорта должен игнорировать изменения, которые внесла эта же интеграция.
Обработать ошибки обмена
Разделяйте ошибки по этапам. Это помогает решить, можно ли повторить операцию.
|
Этап |
Пример ошибки |
Действие |
|
Связь |
Тайм-аут или недоступность внешнего сервиса |
Сохранить попытку и повторить операцию ограниченное число раз |
|
Формат |
Нет обязательного идентификатора или неверный тип значения |
Отклонить сообщение без изменения заказа |
|
Сопоставление |
Неизвестен код статуса, товара или свойства |
Остановить обработку и исправить настройки сопоставления |
|
Бизнес-правило |
Недопустимый переход статуса или изменение оплаченной суммы |
Отклонить изменение и передать конфликт на ручную обработку |
|
Сохранение |
|
Не отмечать сообщение обработанным. Записать сообщения из |
Не записывайте в журнал токены доступа, пароли, платежные реквизиты и полные персональные данные. Для диагностики сохраняйте технические идентификаторы, этап, код ошибки и безопасный фрагмент ответа.
Пример записи ошибки через стандартный журнал проекта:
$orderId = 123;
try
{
// Получите, проверьте и примените сообщение обмена
}
catch (\Throwable $exception)
{
AddMessage2Log(
sprintf(
'Ошибка импорта заказа %d: %s',
$orderId,
$exception->getMessage()
),
'sale_exchange'
);
throw $exception;
}
Функция AddMessage2Log() подходит для технической диагностики, но не заменяет журнал интеграции для контроля повторных сообщений. В журнале должен быть машинно-читаемый статус операции и уникальный идентификатор сообщения.