Custom Delivery Service Handler Development for 1C-Bitrix
Standard 1C-Bitrix delivery services and ready-made Marketplace modules cover most store scenarios. But there are cases where a custom implementation is required: a niche carrier without an existing module, in-house logistics, complex tariff rules, or an aggregator combining multiple carriers in a single handler. In such cases, a custom handler is written from scratch.
Custom Delivery Module Architecture
A delivery service handler in 1C-Bitrix is a PHP class that extends \Bitrix\Sale\Delivery\Services\Base. It is placed in /local/modules/{vendor}.delivery.{name}/ or directly in /local/php_interface/include/sale_delivery/.
File structure for the modular variant:
/local/modules/myshop.delivery.customservice/
include.php — autoloader connection
install/
index.php — module installation class
lib/
DeliveryHandler.php — main handler class
ApiClient.php — delivery service API client
LocationMapper.php — location mapping
TrackingAgent.php — tracking agent
lang/ru/
install/index.php — language phrases
Main Handler Class
namespace Myshop\Delivery\CustomService;
class DeliveryHandler extends \Bitrix\Sale\Delivery\Services\Base
{
protected static $isCalculatePriceImmediately = true;
public static function getClassTitle(): string
{
return 'Custom Delivery Service';
}
public static function getClassDescription(): string
{
return 'Delivery via CustomService API';
}
// Configuration parameters in the admin panel
public function getConfigStructure(): array
{
return [
'MAIN' => [
'TITLE' => 'General Settings',
'ITEMS' => [
'API_KEY' => [
'TYPE' => 'STRING',
'NAME' => 'API Key',
'DEFAULT' => '',
],
'FROM_CITY' => [
'TYPE' => 'STRING',
'NAME' => 'Origin City',
'DEFAULT' => 'Moscow',
],
'AUTO_CREATE' => [
'TYPE' => 'Y/N',
'NAME' => 'Create shipment automatically',
'DEFAULT' => 'N',
],
],
],
];
}
}
Price Calculation Method
The key method is calculateConcrete(). It is called every time the price is calculated on the cart and checkout page.
protected function calculateConcrete(
\Bitrix\Sale\Shipment $shipment
): \Bitrix\Sale\Delivery\CalculationResult {
$result = new \Bitrix\Sale\Delivery\CalculationResult();
// 1. Retrieve required data from the shipment
$order = $shipment->getOrder();
$props = $order->getPropertyCollection();
$toCity = $props->getItemByOrderPropertyCode('CITY')?->getValue();
if (!$toCity) {
$result->addError(new \Bitrix\Main\Error('Delivery city not specified'));
return $result;
}
// 2. Calculate weight
$weight = $this->getShipmentWeight($shipment); // in grams
// 3. Call the API (with caching!)
try {
$apiClient = new ApiClient($this->getOption('API_KEY'));
$price = $apiClient->calcPrice(
$this->getOption('FROM_CITY'),
$toCity,
$weight
);
} catch (\Exception $e) {
$result->addError(new \Bitrix\Main\Error('Calculation error: ' . $e->getMessage()));
return $result;
}
if ($price <= 0) {
$result->addError(new \Bitrix\Main\Error('Delivery to this city is unavailable'));
return $result;
}
// 4. Build the result
$result->setDeliveryPrice($price);
$result->setPeriodDescription('2–5 days');
return $result;
}
Caching in calculateConcrete
The calculateConcrete() method may be called multiple times during a single page load (when the cart is updated). Cache API request results:
private function getCachedPrice(string $fromCity, string $toCity, int $weight): ?float
{
$cacheKey = 'delivery_price_' . md5("{$fromCity}_{$toCity}_{$weight}");
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->startDataCache(1800, $cacheKey, '/delivery/custom')) {
$price = $this->apiClient->calcPrice($fromCity, $toCity, $weight);
$cache->endDataCache(['price' => $price]);
}
return $cache->getVars()['price'];
}
Shipment Creation Method
This method is called when an actual shipment is created — typically when a manager changes the order status.
public function createDelivery(\Bitrix\Sale\Shipment $shipment): array
{
$order = $shipment->getOrder();
$props = $order->getPropertyCollection();
$trackCode = $this->apiClient->createOrder([
'order_id' => $order->getId(),
'recipient' => $this->buildRecipient($props),
'cargo' => $this->buildCargo($shipment),
]);
// Save the tracking number to the shipment
$shipment->setField('TRACKING_NUMBER', $trackCode);
// And to the order properties (for display to the buyer)
$props->getItemByOrderPropertyCode('TRACKING_CODE')?->setValue($trackCode);
$order->save();
return ['trackCode' => $trackCode];
}
Automatic Shipment Creation via Event
To automatically create a shipment when the order status changes:
// In /local/php_interface/init.php
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale', 'OnSaleStatusOrder',
function(\Bitrix\Main\Event $event) {
$orderId = $event->getParameter('id');
$newStatus = $event->getParameter('statusId');
if ($newStatus !== 'PROCESSING') return;
$order = \Bitrix\Sale\Order::load($orderId);
$shipmentCollection = $order->getShipmentCollection();
foreach ($shipmentCollection as $shipment) {
if ($shipment->isSystem()) continue;
$service = \Bitrix\Sale\Delivery\Services\Manager::getObjectById(
$shipment->getDeliveryId()
);
if ($service instanceof DeliveryHandler) {
$service->createDelivery($shipment);
}
}
}
);
Compatibility (isCompatible) and Availability
public function isCompatible(\Bitrix\Sale\Shipment $shipment): bool
{
// Check the country
$location = $this->getShipmentLocation($shipment);
if (!$this->isLocationSupported($location)) return false;
// Minimum order amount
$order = $shipment->getOrder();
if ($order->getPrice() < 500) return false;
// Maximum weight
if ($this->getShipmentWeight($shipment) > 30000) return false;
return true;
}
The isCompatible() method is called by 1C-Bitrix before calculation. If it returns false, the service is not shown to the buyer and no calculation is performed.
Admin Settings Page
Handler parameters are stored in b_sale_delivery_service_params and are accessible via $this->getOption('KEY'). The settings form is generated automatically from the array returned by getConfigStructure(). For non-standard fields (a dropdown list of cities from an API, a map) — override the getEditTemplateHTML() method.
Development Timeline
| Scope | Timeline |
|---|---|
| Basic handler: calculation + creation | 4–5 days |
| + Tracking (polling agent) | +1–2 days |
| + Webhook handler | +1 day |
| + Pickup point widget on checkout | +2 days |
| + Manager dashboard (printing, statuses) | +2–3 days |
A fully custom delivery module "turnkey" — with installer, settings, calculation, shipment creation, tracking, and manager interface — takes 10–14 business days.







