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

Своё ограничение доставки и оплаты - класс, параметры, регистрация

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

Механика

Служба показывается покупателю, только если прошла все свои ограничения. Это отдельный слой: расчёт стоимости отвечает на вопрос «сколько», а ограничение - на вопрос «показывать ли вообще».

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

Своё ограничение пишут, когда правило зависит от данных проекта. Состав корзины, свойства товаров, остаток на конкретном складе, признак договора у покупателя - всё это платформа сама проверить не умеет.

Ограничение - это класс ровно с двумя обязанностями перед платформой. Первая: сказать, какое значение брать из заказа для проверки. Вторая: сравнить это значение с параметрами, заданными в интерфейсе службы.

Регистрируют класс ограничения обработчиком события, а не отдельной записью в базе данных. У доставок, платёжных систем и касс свои списки ограничений и свои события с одинаковым окончанием имени, а обработчик возвращает пару «класс - путь к файлу».

Проверка вызывается часто, поэтому она обязана быть дешёвой по ресурсам. Она отрабатывает при каждом пересчёте формы оформления, а служб на проекте бывает десяток, поэтому запрос к базе внутри проверки быстро становится заметным.

Отказ ограничения покупателю никак не объясняется, и об этом важно помнить заранее. Служба просто исчезает из списка, и это главная особенность механизма: понятное сообщение о причине придётся выводить самому, если оно нужно.

Шаги

  1. Решить, что это: ограничение видимости или условие расчёта стоимости.
  2. Проверить штатные ограничения: возможно, правило уже реализовано ими.
  3. Написать класс с извлечением значения и проверкой параметров.
  4. Описать структуру параметров, которую увидит менеджер в настройках службы.
  5. Зарегистрировать класс обработчиком события нужного списка ограничений.
  6. Проверить правило на трёх корзинах: проходящей, пограничной и запрещённой.

Код

Смотрим, какие ограничения уже стоят:

use Bitrix\Sale\Delivery\Restrictions;
foreach (Restrictions\Manager::getRestrictionsList($deliveryId) as $rule) {
printf("%-40s %s\n", $rule['CLASS_NAME'], $rule['ID']);
}
// служба видна заказу, только если прошла все свои правила

Список ограничений службы - первое, что смотрят при разборе. Половина задач «почему не показывается доставка» решается здесь, без всякого своего кода.

Пишем класс ограничения:

class VendorWeightRestriction extends \Bitrix\Sale\Delivery\Restrictions\Base
{
public static function getClassTitle() { return 'Предельный вес заказа'; }
public static function check($value, array $params, $deliveryId = 0)
{
return (float)$value <= (float)($params['MAX_WEIGHT'] ?? 0);
}
}

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

Говорим, что брать из заказа:

protected static function extractParams(\Bitrix\Sale\Internals\Entity $entity)
{
$order = $entity instanceof \Bitrix\Sale\Order ? $entity : $entity->getCollection()->getOrder();
return $order->getBasket() ? $order->getBasket()->getWeight() : 0;
// сюда приходит и заказ, и отгрузка: тип проверяют явно
}

Метод извлечения - самое хрупкое место класса. В него приходит разная сущность в зависимости от того, откуда идёт проверка, и код обязан пережить оба случая, а не рассчитывать на один.

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

Описываем параметры для интерфейса:

public static function getParamsStructure($entityId = 0)
{
return [
'MAX_WEIGHT' => [
'TYPE' => 'NUMBER',
'LABEL' => 'Максимальный вес заказа, граммы',
'DEFAULT' => 20000,
],
];
}
// поля этой структуры менеджер увидит в настройках службы

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

Регистрируем класс событием:

\Bitrix\Main\EventManager::getInstance()->addEventHandler('sale',
'onSaleDeliveryRestrictionsClassNamesBuildList',
static function () {
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS, [
'\VendorWeightRestriction' => '/local/php_interface/restrictions/weight.php',
]);
});
// у платёжных систем и касс свои события с таким же окончанием имени

Регистрация отдаёт пару «класс и путь к файлу». Автозагрузка здесь не нужна: платформа подключит файл сама, когда дойдёт до построения списка ограничений.

Делаем проверку дешёвой:

public static function check($value, array $params, $deliveryId = 0)
{
static $cache = []; // проверка идёт много раз за оформление
$key = $deliveryId . ':' . $value;
return $cache[$key] ??= self::calculate($value, $params);
}
// запрос к базе внутри проверки умножается на число служб и на число пересчётов

Разбираем исчезнувшую службу:

$order = \Bitrix\Sale\Order::load($orderId);
$shipment = $order->getShipmentCollection()->getNotSystemItems()->current();
var_dump(VendorWeightRestriction::check(
$order->getBasket()->getWeight(), ['MAX_WEIGHT' => 20000], $deliveryId));
// вызов проверки руками показывает, что именно она вернула на этом заказе

Ручной вызов проверки экономит час догадок. Он отвечает на единственный важный вопрос разбора: служба не прошла ваше правило или её отсекло другое, штатное.

Ограничения

Ограничение не заменяет расчёт стоимости. Правило «в этот город возим только самовывозом» - ограничение, а правило «в этот город дороже на пятьсот» - расчёт; смешивать их в одном классе не стоит.

Покупатель не узнает причину отказа. Служба исчезает молча, и если правило неочевидное, рядом со списком доставок выводят своё пояснение - иначе покупатель звонит менеджеру.

Проверка отрабатывает на каждом пересчёте формы. Тяжёлый запрос внутри неё умножается на число служб и число действий покупателя, поэтому данные берут из уже загруженного заказа, а не из базы.

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

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

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

Типичные проблемы

Служба доставки активна, но её нет в форме оформления.

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

Своё ограничение не появилось в настройках службы.

Класс не зарегистрирован обработчиком события списка ограничений или путь к файлу указан неверно. Платформа подключает файл класса сама, по указанному в регистрации пути к нему.

Ограничение работает в форме, но не при создании заказа из кода.

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

Оформление стало заметно медленнее.

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

Проверка падает с ошибкой типа сущности.

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

После обновления модуля правило перестало применяться.

Изменилась сигнатура базового класса или имя события. Свои классы служб проверяют после каждого крупного обновления.

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

Когда писать своё ограничение, а когда хватит штатных?

Штатные закрывают сумму, вес, местоположение, тип плательщика и группу покупателя - это большинство правил. Своё нужно, когда правило смотрит на состав корзины, свойства товаров, остатки или данные покупателя из вашей таблицы.

Почему покупатель не видит причину отказа?

Механизм так устроен: служба, не прошедшая ограничение, просто не попадает в список. Если причину нужно показать, её выводят своим блоком рядом с выбором доставки.

Работает ли тот же приём для платёжных систем?

Да, но со своим базовым классом и своим событием списка ограничений. Логика одинаковая: класс с проверкой и параметрами, регистрация обработчиком, настройка в интерфейсе конкретной системы.

Как отладить ограничение на боевом заказе?

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

Можно ли внутри проверки ходить в базу?

Технически можно, но она вызывается на каждом пересчёте формы для каждой службы. Лучше брать данные из уже загруженного заказа, а результат тяжёлых вычислений кэшировать хотя бы в пределах запроса.

Смежное

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