Обмен заказами связывает объекты модуля sale с учетной, логистической или другой внешней системой. Интеграция выполняет три задачи:
-
получает заказ вместе с корзиной, свойствами, оплатами и отгрузками,
-
сопоставляет внешние и внутренние идентификаторы,
-
применяет входящие изменения через API заказа.
Модуль содержит штатный механизм обмена с 1С и классы пространства имен Bitrix\Sale\Exchange. Для интеграции с произвольной системой можно использовать объектную модель заказа и собственный клиент внешней системы. Выбор зависит от формата данных и протокола внешней системы.
Выбрать сценарий обмена
Импорт и экспорт обозначают направление данных относительно интернет-магазина.
|
|| Сценарий | Направление | Основная задача | Подходящий API ||
|| Экспорт заказа | Из интернет-магазина во внешнюю систему | Прочитать данные заказа и преобразовать их во внешний формат | Класс Bitrix\Sale\Order и связанная объектная модель. Штатный менеджер экспорта для поддерживаемого формата обмена ||
|| Импорт заказа или изменений | Из внешней системы в интернет-магазин | Найти заказ по идентификатору, проверить данные и применить изменения | Штатный менеджер импорта для поддерживаемого формата. Класс Bitrix\Sale\Order для собственной интеграции ||
|| Двусторонняя синхронизация | В обоих направлениях | Сопоставить идентификаторы, статусы и версии данных, исключить повторную обработку | Механизм обмена и журнал интеграции ||
|| Передача заказа в произвольный сервис | Из интернет-магазина во внешний HTTP API или очередь | Определить формат передаваемых данных и обработать ответ сервиса | Класс Bitrix\Sale\Order, клиент внешнего сервиса и журнал интеграции ||
|#
Используйте штатный обмен с 1С для систем, которые поддерживают его формат. Для системы с другим форматом создайте собственный клиент внешнего API. Внутренние классы протокола не заменяют такой клиент.
Выбрать способ интеграции
Для обмена с 1С используйте штатные настройки и обработчики поддерживаемого протокола. Компонент bitrix:sale.export.1c применяет CSaleExport при формировании выгрузки и CSaleOrderLoader при загрузке входящих данных. Не копируйте внутреннюю реализацию обработчика в собственный модуль.
Для произвольной интеграции создайте отдельный слой:
-
Клиент или обработчик внешней системы получает сообщение.
-
Валидатор проверяет формат и состав входящих данных.
-
Слой сопоставления преобразует внешние идентификаторы и состояния во внутренние.
-
Сервис интеграции загружает и изменяет
Bitrix\Sale\Order. -
Журнал интеграции фиксирует попытку, результат и возможность повторного запуска.
При таком разделении код интеграции не зависит от внутреннего формата штатного обмена. Получение данных, сопоставление и изменение заказа можно тестировать отдельно.
Подготовить данные и выбрать API
До запуска обмена подготовьте:
-
настройки штатного обмена или отдельные учетные данные внешнего сервиса,
-
таблицы сопоставления заказов, товаров, статусов, оплат, отгрузок и свойств,
-
свойства заказа для всех типов плательщика, с которыми работает интеграция,
-
журнал сообщений с уникальным идентификатором операции,
-
очередь повторных попыток и правила ручной обработки конфликтов,
-
технического пользователя или другой способ аутентификации входящего запроса.
Проверьте права в точке входа интеграции. Объектная модель заказа не определяет, имеет ли вызывающий пользователь право импортировать данные. Для фонового процесса ограничьте доступ к обработчику, а для операции из пользовательского запроса примените проверки прав проекта до загрузки и изменения заказа.
Определить данные заказа в обмене
Перед передачей данных определите формат и состав данных для обмена. Укажите обязательные поля, внешний идентификатор каждого объекта, формат дат и сумм, правила для пустых значений и допустимые переходы состояния.
|
|| Данные | Объект модуля sale | Что согласовать с внешней системой ||
|| Номер, дата, покупатель, статус, сумма и валюта | Bitrix\Sale\Order | Отличие внутреннего ID от номера заказа и внешнего идентификатора ||
|| Товары, количество, цена, скидка и НДС | Bitrix\Sale\Basket | Идентификатор товара или предложения, единица измерения и правило пересчета цены ||
|| Контактные данные и реквизиты | Bitrix\Sale\PropertyValueCollection | Символьный код свойства, тип значения и правило обработки отсутствующего свойства ||
|| Оплаты | Bitrix\Sale\PaymentCollection | Идентификатор платежа, платежная система, сумма и признак оплаты ||
|| Отгрузки | Bitrix\Sale\ShipmentCollection | Идентификатор отгрузки, служба доставки, состав, трек-номер и признак отгрузки ||
|| Состояние синхронизации | Данные интеграции | Внешний идентификатор, версия сообщения, дата попытки и результат обработки ||
|#
Храните отдельное сопоставление внутреннего и внешнего идентификаторов в данных интеграции. Не используйте номер заказа как единственный ключ синхронизации, если внешняя система может изменить или повторно использовать номер.
Выбрать API механизма обмена
Пространство имен Bitrix\Sale\Exchange содержит инфраструктуру штатного импорта и экспорта. Перед реализацией выберите API по задаче интеграции.
|
|| API | Роль ||
|| Bitrix\Sale\Exchange\ManagerImport | Регистрирует настройки, правила разрешения коллизий и критерии поиска для типов импортируемых объектов ||
|| Bitrix\Sale\Exchange\ManagerExport | Регистрирует настройки типов экспортируемых объектов и определяет направление и режим штатного обмена ||
|| Bitrix\Sale\Exchange\Entity\EntityImport | Внутренний базовый класс для импорта заказа, оплат, отгрузок и профиля покупателя ||
|| Bitrix\Sale\Order | Загружает и изменяет заказ вместе со связанными коллекциями ||
|| Bitrix\Sale\TradingPlatform\Manager | Возвращает активные торговые платформы и объект платформы по идентификатору ||
|| Bitrix\Sale\TradingPlatform\Platform | Базовый класс обработчика торговой платформы ||
|#
Классы 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.
Работа компонента зависит от настроек модуля.
|
|| Настройка | Значение и роль | Значение по умолчанию ||
|| 1C_SALE_SITE_LIST | Строковый идентификатор сайта ограничивает заказы, которые участвуют в обмене | Пустая строка — без фильтра по сайту ||
|| 1C_EXPORT_PAYED_ORDERS | Y разрешает экспортировать только оплаченные заказы | Пустая строка — фильтр выключен ||
|| 1C_EXPORT_ALLOW_DELIVERY_ORDERS | Y разрешает экспортировать только заказы с разрешенной доставкой | Пустая строка — фильтр выключен ||
|| 1C_EXPORT_FINAL_ORDERS | Строковый код статуса задает начальный статус отбора. Механизм включает его и следующие статусы по сортировке | Пустая строка — фильтр выключен ||
|| 1C_CHANGE_STATUS_FROM_1C | Y разрешает менять статус заказа по входящим данным 1С | Пустая строка — изменение выключено ||
|| 1C_IMPORT_NEW_ORDERS | Y разрешает импортировать новые заказы, N запрещает | N ||
|| 1C_SALE_GROUP_PERMISSIONS | Строка с идентификаторами групп через запятую задает доступ к точке обмена | 1 ||
|#
Настраивайте эти параметры в интерфейсе модуля. Не вызывайте компонент или служебную точку из 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::STATUS_ID | Разрешайте только переходы, которые поддерживает процесс магазина ||
|| Состояние оплаты | Объект Payment | Найдите конкретную оплату по сохраненному соответствию и изменяйте ее через объектную модель ||
|| Состояние доставки | Объект Shipment | Не смешивайте статус отгрузки, разрешение доставки и трек-номер ||
|| Товар | Позиция BasketItem | Сопоставляйте внешний код с товаром или торговым предложением и проверяйте единицу измерения ||
|| Реквизит покупателя | Значение свойства заказа | Сопоставляйте по стабильному символьному коду, а не по отображаемому названию ||
|#
Одна внешняя операция может изменить несколько частей заказа. Применяйте связанные изменения к одному загруженному объекту и вызывайте Order::save() один раз после проверок и необходимых расчетов.
Не подтверждайте оплату только по общему статусу внешнего заказа. Для финансовой операции нужны идентификатор конкретного платежа, сумма, валюта и подтвержденный ответ платежной или учетной системы.
Сохранить свойства заказа при импорте
Коллекция свойств содержит только свойства, доступные заказу с его типом плательщика. Импорт должен обновлять выбранные значения и не заменять всю коллекцию входным массивом.
Для каждого входящего свойства:
-
Найдите внутреннее свойство по заранее настроенному символьному коду.
-
Проверьте тип и допустимый формат значения.
-
Измените найденный объект значения свойства.
-
Оставьте остальные свойства без изменений.
Если свойство не найдено, не создавайте его автоматически в процессе импорта. Зафиксируйте ошибку сопоставления и настройте свойство для нужного типа плательщика.
Как обеспечить надежность обмена
Надежный обмен должен корректно обрабатывать повторные сообщения, одновременные изменения и ошибки. Настройте повторный запуск, разрешение конфликтов и журналирование.
Обеспечить повторный запуск
Внешняя система может повторно отправить сообщение после тайм-аута или из-за того, что не получила ответ интернет-магазина. Обрабатывайте повторное сообщение без повторного изменения оплаты, количества товара или статуса.
Сохраните в журнале интеграции:
-
идентификатор источника и сообщения,
-
внутренний и внешний идентификаторы заказа,
-
тип операции и версию данных,
-
время получения и обработки,
-
результат и текст ошибки,
-
контрольную сумму значимых данных, если внешний протокол не передает версию.
Перед изменением заказа проверьте уникальность сообщения. Записывайте успешный результат после Order::save(). Если внешний протокол поддерживает подтверждение, отправляйте его после фиксации результата.
Обработать конкурирующие изменения
Импорт может выполняться одновременно с редактированием заказа менеджером или другим интеграционным процессом. До изменения сравните версию внешних данных с последним обработанным состоянием. Заранее определите владельца каждого поля. Укажите, какая система может менять значение и чьи данные имеют приоритет при конфликте.
Сначала выполните HTTP-запрос к внешней системе и проверьте сообщение. Затем измените заказ локально. Если изменение выполняется в транзакции базы данных, завершите ее сразу после попытки сохранить заказ. При успешном сохранении зафиксируйте изменения, при ошибке — откатите. Для очереди повторно загрузите заказ непосредственно перед изменением.
Если конфликт нельзя разрешить автоматически, не перезаписывайте локальные данные. Зафиксируйте конфликт в журнале и передайте его на ручную обработку.
Изменение и сохранение заказа может запускать стандартные события и другие обработчики проекта. Проверяйте источник операции перед обратным экспортом, иначе входящий импорт может запустить рекурсивный обмен. Сохраняйте признак источника вместе с данными операции. Обработчик экспорта должен игнорировать изменения, которые внесла эта же интеграция.
Обработать ошибки обмена
Разделяйте ошибки по этапам. Это помогает решить, можно ли повторить операцию.
|
|| Этап | Пример ошибки | Действие ||
|| Связь | Тайм-аут или недоступность внешнего сервиса | Сохранить попытку и повторить операцию ограниченное число раз ||
|| Формат | Нет обязательного идентификатора или неверный тип значения | Отклонить сообщение без изменения заказа ||
|| Сопоставление | Неизвестен код статуса, товара или свойства | Остановить обработку и исправить настройки сопоставления ||
|| Бизнес-правило | Недопустимый переход статуса или изменение оплаченной суммы | Отклонить изменение и передать конфликт на ручную обработку ||
|| Сохранение | Order::save() вернул ошибки | Не отмечать сообщение обработанным. Записать сообщения из Result ||
|#
Не записывайте в журнал токены доступа, пароли, платежные реквизиты и полные персональные данные. Для диагностики сохраняйте технические идентификаторы, этап, код ошибки и безопасный фрагмент ответа.
Пример записи ошибки через стандартный журнал проекта:
$orderId = 123;
try
{
// Получите, проверьте и примените сообщение обмена
}
catch (\Throwable $exception)
{
AddMessage2Log(
sprintf(
'Ошибка импорта заказа %d: %s',
$orderId,
$exception->getMessage()
),
'sale_exchange'
);
throw $exception;
}
Функция AddMessage2Log() подходит для технической диагностики, но не заменяет журнал интеграции для контроля повторных сообщений. В журнале должен быть машинно-читаемый статус операции и уникальный идентификатор сообщения.