Оформление заказа и публичные сценарии
- Подключить компонент
- Проследить путь от корзины до оплаты
- Подготовить корзину к оформлению
- Сопоставить данные формы с объектами заказа
- Пересчитать заказ после выбора покупателя
- Проверить заказ перед подтверждением
- Сохранить заказ
- Показать подтверждение и перейти к оплате
- Разрешить оплату после проверки менеджером
- Изменить оформление через события компонента
- Продолжить работу с заказом
Публичный компонент оформления заказа связывает корзину покупателя с его контактными данными, доставкой и оплатой.
При первом открытии страницы и каждом AJAX-пересчете заказ существует только в памяти. Компонент сохраняет его после подтверждения формы и успешной проверки данных, а затем показывает страницу заказа и доступные способы оплаты.
До подключения компонента настройте типы плательщиков, свойства заказа, службы доставки и платежные системы. Для создания заказа без публичной формы используйте сценарий Создание заказа.
Подключить компонент
Компонент bitrix:sale.order.ajax получает выбранные значения из формы и собирает объект Bitrix\Sale\Order.
Разместите вызов компонента на публичной странице после подключения пролога. Пролог запускает ядро системы и создает глобальный объект $APPLICATION, через который подключают компоненты.
В примере указаны основные параметры сценария:
$APPLICATION->IncludeComponent(
'bitrix:sale.order.ajax',
'',
[
'PATH_TO_BASKET' => '/personal/cart/',
'PATH_TO_PERSONAL' => '/personal/order/',
'PATH_TO_PAYMENT' => '/personal/order/payment/',
'ALLOW_AUTO_REGISTER' => 'Y',
]
);
-
PATH_TO_BASKETзадает страницу корзины, -
PATH_TO_PERSONAL— раздел с заказами покупателя, -
PATH_TO_PAYMENT— отдельную страницу запуска платежного обработчика.
При ALLOW_AUTO_REGISTER = Y компонент может автоматически зарегистрировать неавторизованного покупателя, если это разрешают настройки регистрации. При значении N он показывает форму авторизации.
Параметры в примере задают пути сценария и режим авторегистрации.
Проследить путь от корзины до оплаты
Данные формы проходят через объекты заказа в определенном порядке.
|
Шаг оформления |
Данные формы |
Объект |
|
Подготовка товаров |
Состав и количество товаров в корзине |
|
|
Выбор покупателя |
|
|
|
Контактные и адресные данные |
|
|
|
Выбор доставки |
|
|
|
Выбор оплаты |
|
|
|
Комментарий покупателя |
|
Поле |
|
Подтверждение |
|
|
До подтверждения изменения формы относятся только к текущему расчету. Перед сохранением компонент дополнительно проверяет сессию и собранные данные.
Подготовить корзину к оформлению
Идентификатор FUSER связывает корзину с текущим посетителем, в том числе до авторизации. По FUSER и идентификатору сайта компонент загружает корзину, вызывает Basket::refresh(), сохраняет обновленные данные и получает позиции через Basket::getOrderableItems().
В заказ попадают только позиции, которые доступны для покупки и не отложены. Если доступных позиций нет, компонент показывает пустую корзину или перенаправляет покупателя в зависимости от своих параметров.
Перед сохранением компонент повторно загружает корзину. В AJAX-сценарии он сравнивает текущее количество каждой доступной позиции с количеством этой позиции, которое участвовало в предыдущем расчете. Если состав изменился, компонент устанавливает заказу поле MARKED = Y и добавляет диагностический маркер с кодом SALE_ORDER_CONSISTENCY_CHANGED_ERROR. Такая пометка не отменяет вызов Order::save().
После сохранения проверяйте $order->getField('MARKED') === 'Y' перед необратимыми действиями:
-
передачей заказа на сборку,
-
списанием денег,
-
отправкой данных во внешнюю систему.
Помеченный заказ направьте на проверку менеджеру либо предложите покупателю вернуться к оформлению и подтвердить актуальный состав корзины. Код маркера нужен для диагностики причины, а поле MARKED — для ветвления бизнес-сценария.
Если позиции нужно подготовить до запуска оформления, используйте сценарии из раздела Корзина.
Сопоставить данные формы с объектами заказа
Из HTTP-запроса компонент формирует внутренний набор данных покупателя. По этим данным он создает и заполняет заказ. Компонент назначает тип плательщика и добавляет свойства, корзину, отгрузку и оплаты. Доставка и платежная система влияют друг на друга через ограничения, поэтому после их выбора компонент пересчитывает заказ.
Выбрать тип плательщика
Поле PERSON_TYPE содержит идентификатор типа плательщика. Компонент получает активные типы плательщиков текущего сайта и передает выбранный идентификатор в Order::setPersonTypeId().
Если переданный идентификатор недоступен, компонент выбирает первый тип из доступного списка. При смене типа плательщика меняется набор свойств заказа, профилей покупателя, платежных систем и других зависимых данных.
Заполнить свойства заказа
Форма передает обычные и файловые свойства в полях ORDER_PROP_<ID>. Компонент выбирает свойства для текущего типа плательщика и заполняет PropertyValueCollection методом setValuesFromPost().
Значение может поступить из формы, профиля покупателя, данных пользователя или значения свойства по умолчанию. Для местоположения и индекса компонент также может использовать данные геолокации, если эта возможность включена в параметрах.
При подтверждении заказа компонент вызывает для каждого несистемного значения свойства verify() и checkRequiredValue(). Ошибки попадают в блок свойств и не дают сохранить заказ.
При обработке нестандартных полей сверяйтесь с кодами и типами из раздела Свойства заказа.
Выбрать доставку
Для выбранной доставки компонент создает одну пользовательскую отгрузку и переносит в нее все позиции корзины с полным количеством. В коллекции также есть служебная системная отгрузка, но она не представляет выбранную покупателем доставку.
Поле DELIVERY_ID задает службу доставки. Компонент проверяет доступные службы с учетом ограничений. Если выбранная служба недоступна, он использует первую доступную службу и добавляет предупреждение об изменении выбора.
После выбора компонент заполняет в Shipment поля DELIVERY_ID, DELIVERY_NAME и CURRENCY. Для самовывоза он записывает склад, а для дополнительных услуг передает значения из DELIVERY_EXTRA_SERVICES. Затем ShipmentCollection::calculateDelivery() рассчитывает доставку.
Чтобы расчет альтернативных служб не изменил текущий заказ, компонент использует его временную копию. Выбранную службу он всегда рассчитывает на текущем объекте заказа. Раздел Доставка и отгрузки поможет рассчитать службу вручную или изменить отгрузку после оформления.
Выбрать способ оплаты
Поле PAY_SYSTEM_ID содержит идентификатор платежной системы. По выбранному идентификатору компонент создает объект оплаты, задает сумму и получает доступные платежные системы через PaySystem\Manager::getListWithRestrictions().
Если выбранная платежная система недоступна, компонент выбирает первую доступную внешнюю систему и добавляет предупреждение. Когда доступных систем нет, компонент возвращает ошибку блока оплаты.
Внутренний счет хранит доступные покупателю средства внутри магазина. Если такая оплата включена, компонент может создать внутреннюю оплату, а внешней платежной системе передать оставшуюся сумму заказа. После изменения стоимости заказа или доставки компонент заново распределяет суммы между внутренней и внешней оплатами.
Для запуска обработчика или изменения коллекции оплат после оформления перейдите к разделу Оплаты и платежные системы.
Пересчитать заказ после выбора покупателя
Изменение местоположения, индекса, службы доставки или платежной системы может изменить доступность сервисов и итоговую стоимость. Поэтому ответ на AJAX-запрос нужно воспринимать как новый расчет, а не как частичное обновление одного поля.
При пересчете компонент заново выполняет основной сценарий:
-
Создает объект заказа для сайта и покупателя.
-
Заполняет тип плательщика и свойства.
-
Обновляет корзину и переносит доступные позиции в заказ.
-
Создает отгрузку и выполняет промежуточный расчет заказа.
-
Выбирает службу доставки и платежную систему в порядке, который задан зависимостью между этими сервисами, и создает оплаты.
-
Повторяет зависимые расчеты, проверяет доступность выбранных сервисов и пересчитывает суммы оплат по итоговой стоимости заказа.
-
Формирует данные для шаблона и JavaScript.
Объект этого расчета существует только в памяти. Следующий запрос компонента соберет его заново из актуальной корзины, формы и настроек сайта.
Компонент может вызывать Order::doFinalAction(true) несколько раз во время одного пересчета. Не используйте расположение одного вызова как универсальный порядок для собственного серверного сценария. Сначала соберите зависимые объекты, затем синхронизируйте итоговые суммы и повторно проверьте ограничения перед сохранением.
Проверить заказ перед подтверждением
Кнопка подтверждения должна отправить confirmorder=Y. Компонент считает заказ подтвержденным только для POST-запроса с действующей сессией.
Перед сохранением компонент проверяет:
-
что корзина содержит доступные для заказа позиции,
-
компонент получил доступный тип плательщика,
-
покупатель заполнил обязательные свойства, а их значения прошли проверку,
-
компонент нашел доступную службу доставки или назначил системную
EmptyDeliveryService, если доступных служб нет, -
компонент нашел доступную платежную систему,
-
покупатель авторизовался или зарегистрировался по сценарию компонента.
После этих проверок Order::save() выполняет проверки и события объектной модели. Любой вызов setField(), расчет доставки, обработчик события или сохранение может вернуть ошибки. Не определяйте готовность заказа только по значениям, которые показаны в браузере.
Сохранить заказ
Одним вызовом Order::save() компонент сохраняет заказ и связанные с ним объекты: корзину, свойства, оплаты и отгрузки. При успехе результат содержит внутренний идентификатор заказа, а компонент получает номер заказа из поля ACCOUNT_NUMBER.
Если Order::save() возвращает ошибки, компонент добавляет их в общий блок результата и оставляет форму в режиме оформления.
После успешного сохранения компонент:
-
очищает кеш цен и данные о количестве товаров в корзине,
-
сохраняет профиль покупателя,
-
регистрирует согласия.
Идентификатор заказа для автоматически зарегистрированного покупателя компонент также сохраняет в сессии, чтобы покупатель мог открыть подтверждение в рамках текущей сессии.
Показать подтверждение и перейти к оплате
После сохранения компонент перенаправляет покупателя на страницу оформления, где размещен bitrix:sale.order.ajax, и добавляет параметр ORDER_ID. Значением служит ACCOUNT_NUMBER.
Страница подтверждения загружает заказ по номеру. Компонент показывает данные только владельцу заказа или покупателю, для которого идентификатор заказа сохранился в текущей сессии.
Если номер из URL нужно обработать в собственном коде, используйте Order::loadByAccountNumber($accountNumber). Метод Order::load($id) принимает внутренний идентификатор из базы данных, поэтому передавать ему значение параметра ORDER_ID нельзя.
Платежный блок появляется при выполнении трех условий:
-
статус заказа разрешает оплату,
-
в заказе есть неоплаченная оплата с платежной системой,
-
обработчик платежной системы успешно подготовил форму или ссылку.
Для обработчика, который работает в текущем окне, компонент вызывает PaySystem\Service::initiatePay() и выводит подготовленный шаблон. Для обработчика в новом окне ссылка ведет на страницу из параметра PATH_TO_PAYMENT и содержит номера заказа и оплаты.
Стандартная страница с компонентом bitrix:sale.order.payment повторно проверяет доступ. Она разрешает оплату в одном из трех случаев:
-
заказ открыл его владелец,
-
идентификатор заказа сохранен в сессии покупателя,
-
гостевая ссылка содержит корректный хеш, а гостевой просмотр разрешен.
Не публикуйте ссылку только с идентификатором заказа: такая ссылка не должна давать доступ к оплате.
Разрешить оплату после проверки менеджером
Для отложенной оплаты используйте два статуса заказа:
-
статус ожидания проверки, который расположен до статуса разрешения оплаты,
-
статус после проверки, который совпадает со статусом разрешения оплаты или расположен после него.
Границу задает параметр модуля Интернет-магазин «Статус, начиная с которого можно оплатить заказ». В коде ему соответствует опция sale.allow_pay_status. Метод Bitrix\Sale\OrderStatus::isAllowPay() разрешает оплату для выбранного статуса и всех следующих статусов согласно их сортировке. Статусы до этой границы запрещают оплату. Компонент может сохранить оплату вместе с заказом, но не покажет платежную форму, пока текущий статус запрещает оплату.
После проверки менеджер переводит заказ в статус, который разрешает оплату.
В примере $internalOrderId — внутренний идентификатор заказа из базы данных, а не ACCOUNT_NUMBER из параметра ORDER_ID на странице подтверждения.
$order = \Bitrix\Sale\Order::load($internalOrderId);
if (!$order)
{
throw new \RuntimeException('Заказ не найден');
}
$statusPaymentAllowed = 'P'; // замените на статус проекта
if (!\Bitrix\Sale\OrderStatus::isAllowPay($statusPaymentAllowed))
{
throw new \RuntimeException('Выбранный статус не разрешает оплату');
}
$result = $order->setField('STATUS_ID', $statusPaymentAllowed);
if (!$result->isSuccess())
{
throw new \RuntimeException(implode('; ', $result->getErrorMessages()));
}
$saveResult = $order->save();
if (!$saveResult->isSuccess())
{
throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
}
После смены статуса покупатель может открыть оплату из личного раздела или по разрешенной гостевой ссылке. Страница оплаты еще раз проверит статус перед вызовом initiatePay().
Если компонент не создал оплату при оформлении, после проверки создайте ее, назначьте платежную систему, укажите сумму и сохраните заказ. Выполните эти шаги по сценарию Разрешить оплату после проверки менеджером.
Изменить оформление через события компонента
События позволяют изменить стандартное поведение оформления. Когда компонент доходит до определенного этапа, событие передает обработчику данные для проверки или дополнения.
Используйте события компонента, если логика относится именно к публичному оформлению. Для общих правил заказа, которые должны работать в административном разделе, фоновых задачах, используйте события объектной модели sale.
Выбрать событие
Выбирайте событие по таблице. Для каждого события указаны момент вызова и доступное изменение.
|
Событие |
Момент вызова |
Сценарий |
|
|
После чтения данных формы, до создания заказа |
Нормализовать или дополнить входные данные компонента |
|
|
После подготовки значений свойств, до записи в коллекцию |
Изменить значения свойств из формы |
|
|
После сборки и основного пересчета объекта заказа, до сохранения |
Изменить объект заказа или зависимые данные и запросить повторный расчет |
|
|
После расчета вариантов доставки |
Дополнить или отфильтровать данные доставки в результате компонента |
|
|
После подготовки результата и данных для шаблона |
Добавить или изменить данные в результате компонента |
|
|
После подготовки данных для JavaScript |
Изменить |
|
|
Перед отправкой AJAX-ответа |
Дополнить итоговый JSON-ответ |
|
|
После попытки сохранить заказ |
Выполнить действие, которому нужен идентификатор созданного заказа. Перед действием нужно учитывать ошибки сохранения |
В новых проектах используйте OnSaleComponentOrderCreated и OnSaleComponentOrderResultPrepared. События OnSaleComponentOrderOneStep* относятся к старому совместимому механизму: компонент передает в них массивы данных и синхронизирует изменения с объектом заказа.
Передать параметры в обработчик
События компонента передают обработчику аргументы в установленном порядке. Регистрируйте обработчики через EventManager::addEventHandlerCompatible(), чтобы получить эти аргументы и изменить переданные по ссылке массивы.
Разместите регистрацию в /local/php_interface/init.php или в подключаемом из него файле. Код должен выполняться на каждом запросе до запуска компонента. К моменту вызова событий компонент уже загрузит модуль sale.
Не регистрируйте обработчик в шаблоне компонента или внутри другого обработчика. Это может привести к повторной регистрации.
В сигнатурах ниже Order означает Bitrix\Sale\Order, а HttpRequest — Bitrix\Main\HttpRequest.
|
Событие |
Параметры обработчика |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Знак & означает, что обработчик может изменить исходный массив компонента. Аргумент без & обработчик получает по значению. Объект Order можно менять его методами: обработчик получает тот же экземпляр, который собрал компонент.
|
Параметр |
Содержимое и назначение |
|
|
Нормализованный выбор покупателя: тип плательщика, свойства, доставка, платежная система и служебные флаги пересчета. Компонент использует изменения массива при сборке заказа |
|
|
Текущий HTTP-запрос. Используйте его только для данных, которых нет в нормализованном |
|
|
Параметры экземпляра компонента, включая пути, режим регистрации и настройки отображения |
|
|
Результат для шаблона. В событии |
|
|
Содержит доступные с учетом ограничений службы доставки. Ключ массива — идентификатор службы |
|
|
Содержит доступные с учетом ограничений платежные системы. Ключ массива — идентификатор платежной системы |
|
|
Итоговый массив AJAX-ответа перед преобразованием в JSON. Изменение массива не меняет заказ |
|
|
Внутренний идентификатор сохраненного заказа. При ошибке сохранения значение может быть пустым |
|
|
Снимок полей заказа для совместимого события завершения. Обработчик получает копию массива, поэтому его изменение не обновляет заказ |
Управлять пересчетом
В OnSaleComponentOrderCreated установите служебный флаг в $arUserResult:
|
Флаг |
Когда использовать |
Действие компонента |
|
|
Изменились исходные данные, от которых зависят несколько частей заказа: тип плательщика, свойства, доставка или платежная система |
Компонент полностью повторяет сборку заказа, после чего продолжает обработку уже с новым объектом |
|
|
Изменился выбор платежной системы или данные, влияющие только на оплаты |
Компонент повторно рассчитывает коллекцию оплат после возможной повторной сборки |
По умолчанию оба флага отсутствуют, поэтому компонент не запускает дополнительный цикл. Устанавливайте значение строкой 'Y'. Повторные вызовы при неизменных данных должны давать одинаковый результат, иначе пересборка может менять выбор бесконечно.
Компонент вызывает OnSaleComponentOrderOneStepComplete после попытки Order::save() независимо от результата. При ошибке сохранения идентификатор заказа может отсутствовать, поэтому обработчик должен проверить $orderId перед дальнейшими действиями.
Добавить данные в результат компонента
Событие OnSaleComponentOrderResultPrepared подходит для данных, которые нужны шаблону оформления. Пример добавляет цену и тип плательщика текущего несохраненного заказа в отдельный ключ результата.
use Bitrix\Main\EventManager;
use Bitrix\Main\HttpRequest;
use Bitrix\Sale\Order;
EventManager::getInstance()->addEventHandlerCompatible(
'sale',
'OnSaleComponentOrderResultPrepared',
static function (
Order $order,
array &$arUserResult,
HttpRequest $request,
array &$arParams,
array &$arResult
): void
{
$arResult['CUSTOM_CHECKOUT_DATA'] = [
'ORDER_PRICE' => $order->getPrice(),
'PERSON_TYPE_ID' => $order->getPersonTypeId(),
];
}
);
Компонент передаст CUSTOM_CHECKOUT_DATA в шаблон вместе с остальным $arResult. Обработчик не сохраняет заказ и не создает внешних данных.
Пересчитать оплату после изменения выбора
Событие OnSaleComponentOrderCreated получает собранный объект заказа и доступные платежные системы. Обработчик в примере выбирает платежную систему для заказов от заданной суммы и устанавливает флаг повторного расчета оплат.
use Bitrix\Main\EventManager;
use Bitrix\Main\HttpRequest;
use Bitrix\Sale\Order;
$preferredPaySystemId = 5; // замените на идентификатор платежной системы проекта
$minimumOrderPrice = 10000;
EventManager::getInstance()->addEventHandlerCompatible(
'sale',
'OnSaleComponentOrderCreated',
static function (
Order $order,
array &$arUserResult,
HttpRequest $request,
array &$arParams,
array &$arResult,
array &$arDeliveryServiceAll,
array &$arPaySystemServiceAll
) use ($preferredPaySystemId, $minimumOrderPrice): void
{
if (
$order->getPrice() >= $minimumOrderPrice
&& isset($arPaySystemServiceAll[$preferredPaySystemId])
)
{
$arUserResult['PAY_SYSTEM_ID'] = $preferredPaySystemId;
$arUserResult['CALCULATE_PAYMENT'] = 'Y';
}
}
);
После события компонент проверяет CALCULATE_PAYMENT и повторно рассчитывает оплаты. Если обработчик меняет данные, которые требуют полной повторной сборки заказа, установите $arUserResult['RECREATE_ORDER'] = 'Y'. Не вызывайте Order::save() внутри события. Штатный сценарий сохранит заказ после подтверждения и всех проверок.
OnSaleComponentOrderShowAjaxAnswer изменяет только AJAX-ответ, а OnSaleComponentOrderOneStepComplete завершает попытку сохранения.
В обработчиках пересчета не создавайте записи, не отправляйте уведомления и не меняйте повторно сумму при каждом одинаковом запросе.
Продолжить работу с заказом
После сохранения публичный заказ ничем не отличается от заказа, который создали вручную через API. Загрузите заказ методом Order::load(). Затем измените его свойства, оплаты, отгрузки или статус и сохраните одним вызовом Order::save().
Выберите продолжение сценария:
-
соберите заказ без компонента по статье Создание заказа,
-
загрузите и измените сохраненный заказ по статье Изменение и чтение заказа,
-
запустите или повторите платежный сценарий по статье Оплаты и платежные системы,
-
рассчитайте доставку или измените отгрузку по статье Доставка и отгрузки.
Публичный компонент отвечает за ввод, пересчет и подтверждение. Объектная модель отвечает за состояние заказа и остается основным API для дальнейшей обработки.