Developing a custom 1C-Bitrix delivery service handler

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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.