Разработка кастомного обработчика службы доставки 1С-Битрикс
Когда готовых модулей для нужного перевозчика не существует, API службы доставки принципиально отличается от стандартных паттернов, или бизнес-логика расчёта настолько специфична, что ни один существующий обработчик не подходит — разрабатывается кастомный обработчик. Это не экзотика: собственный транспортный отдел, калькулятор с фрахтованием, региональный перевозчик без готового модуля.
Базовая архитектура обработчика
Обработчик доставки в Битрикс наследует от \Bitrix\Sale\Delivery\Services\Base и реализует несколько ключевых методов:
namespace Local\Delivery;
use Bitrix\Main\Localization\Loc;
use Bitrix\Sale\Delivery\Services\Base;
use Bitrix\Sale\Delivery\CalculationResult;
use Bitrix\Sale\Shipment;
class CustomDeliveryService extends Base
{
// Отображаемое название в списке служб доставки
protected static function getClassTitle(): string
{
return 'Собственная доставка';
}
// Описание — показывается в административной части
protected static function getClassDescription(): string
{
return 'Расчёт стоимости доставки через собственный транспортный отдел';
}
// Поддерживаем ли расчёт стоимости (да — иначе нельзя получить цену)
public static function canHasProfiles(): bool { return false; }
public static function whetherAdminExist(): bool { return false; }
public static function isCompatible(\Bitrix\Sale\Shipment $shipment): bool { return true; }
// Поля настройки обработчика в административной части
protected function getConfigStructure(): array
{
return [
'main' => [
'title' => 'Настройки',
'items' => [
'API_URL' => [
'title' => 'URL API перевозчика',
'type' => 'text',
],
'API_KEY' => [
'title' => 'Ключ API',
'type' => 'text',
],
'FROM_CITY' => [
'title' => 'Город отправки',
'type' => 'text',
'default' => 'Москва',
],
'PRICE_PER_KG' => [
'title' => 'Цена за кг (руб.)',
'type' => 'text',
'default' => '150',
],
'BASE_PRICE' => [
'title' => 'Базовая стоимость (руб.)',
'type' => 'text',
'default' => '300',
],
],
],
];
}
// Основной метод расчёта — обязателен
protected function calculateConcrete(Shipment $shipment): CalculationResult
{
$result = new CalculationResult();
try {
$price = $this->calcDeliveryPrice($shipment);
$result->setDeliveryPrice($price);
$result->setPeriodDescription($this->estimatePeriod($shipment));
} catch (\Throwable $e) {
$result->addError(new \Bitrix\Main\Error($e->getMessage()));
}
return $result;
}
}
Логика расчёта: собственный тариф
Типичный кастомный расчёт — комбинация фиксированной базовой ставки и переменной части (по весу, объёму, расстоянию):
private function calcDeliveryPrice(Shipment $shipment): float
{
$order = $shipment->getOrder();
$weightKg = $shipment->getWeight() / 1000;
$basePrice = (float)$this->getOption('BASE_PRICE', 300);
$pricePerKg = (float)$this->getOption('PRICE_PER_KG', 150);
// Базовая цена + цена за вес
$price = $basePrice + ($weightKg * $pricePerKg);
// Надбавка за объёмный вес
$volumeWeight = $this->getVolumeWeight($shipment);
if ($volumeWeight > $weightKg) {
$price = $basePrice + ($volumeWeight * $pricePerKg);
}
// Скидка при заказе от 10 000 руб.
if ($order->getPrice() >= 10000) {
$price *= 0.9;
}
// Минимальная стоимость доставки
return max($price, $basePrice);
}
private function getVolumeWeight(Shipment $shipment): float
{
// Объёмный вес по формуле Д×Ш×В / 5000
// Размеры берём из настроек или из свойств товаров
$length = (float)$this->getOption('DEFAULT_LENGTH', 20); // см
$width = (float)$this->getOption('DEFAULT_WIDTH', 20);
$height = (float)$this->getOption('DEFAULT_HEIGHT', 20);
return ($length * $width * $height) / 5000;
}
Вызов внешнего API
Когда расчёт нельзя сделать локально и нужен API перевозчика:
private function apiCalc(Shipment $shipment): array
{
$order = $shipment->getOrder();
$toCity = $this->getOrderCity($shipment);
$payload = [
'from' => $this->getOption('FROM_CITY'),
'to' => $toCity,
'weight' => $shipment->getWeight() / 1000,
'amount' => round($order->getPrice()),
];
$ch = curl_init($this->getOption('API_URL') . '/calculate');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5, // жёсткий таймаут — не тормозим оформление заказа
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Api-Key: ' . $this->getOption('API_KEY'),
],
]);
$response = json_decode(curl_exec($ch), true);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 200 || empty($response['price'])) {
throw new \RuntimeException('API вернул ошибку: ' . $code);
}
return $response;
}
Таймаут 5 секунд — критически важен. Медленный API перевозчика не должен замораживать страницу оформления заказа для покупателя.
Кэширование расчётов
Расчёт доставки вызывается при каждом изменении корзины. Если API медленный — кэшируем:
private function calcWithCache(Shipment $shipment): float
{
$cacheKey = 'delivery_calc_' . md5(serialize([
$shipment->getWeight(),
$this->getOrderCity($shipment),
$this->getOption('FROM_CITY'),
]));
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(300, $cacheKey, '/delivery/')) { // 5 минут
return $cache->getVars();
}
$price = $this->apiCalc($shipment)['price'];
$cache->startDataCache();
$cache->endDataCache($price);
return (float)$price;
}
Регистрация обработчика
// local/init.php
\Bitrix\Main\Loader::registerAutoLoadClasses(null, [
'Local\\Delivery\\CustomDeliveryService' => '/local/php_interface/delivery/CustomDeliveryService.php',
]);
\Bitrix\Sale\Delivery\Services\Manager::register('Local\\Delivery\\CustomDeliveryService');
После регистрации обработчик появляется в списке служб доставки в административной части и доступен для создания экземпляров с индивидуальными настройками.
Кейс: расчёт с фрахтованием
Производственная компания, доставка собственным автопарком по РФ. Стоимость рассчитывается по матрице: базовая ставка по направлению (откуда → куда) плюс надбавка за вес и объём. Матрица тарифов хранится в инфоблоке (150 строк: город отправки × город получения). Обработчик при расчёте ищет строку матрицы по парам городов, применяет тарифные коэффициенты. При отсутствии прямого маршрута — отображает сообщение «Свяжитесь с менеджером для расчёта доставки».
Сроки
| Состав | Срок |
|---|---|
| Базовый обработчик (локальный расчёт) | 2–3 дня |
| + Интеграция с внешним API перевозчика | +2–3 дня |
| + Создание заказов + трекинг | +2–3 дня |
| + Матрица тарифов / сложная логика | +2–4 дня |







