Интеграция службы доставки DHL на сайт

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Интеграция службы доставки DHL на сайт
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Интеграция службы доставки DHL на сайт

DHL — глобальный логистический оператор, ключевой выбор для международной доставки. API DHL Express (для срочных отправлений) и DHL eCommerce (для интернет-торговли) — разные продукты с разными endpoint'ами и авторизацией. Для российского интернет-магазина с международными отправками чаще используют DHL Express API.

Авторизация

DHL Express API использует Basic Auth с API key и API secret:

class DhlExpressClient
{
    private const BASE_URL = 'https://express.api.dhl.com/mydhlapi';

    public function __construct(
        private string $apiKey,
        private string $apiSecret,
        private bool   $sandbox = false
    ) {
        if ($sandbox) {
            // Sandbox: другой URL
            // https://express.api.dhl.com/mydhlapi/test
        }
    }

    public function request(string $method, string $path, array $params = []): array
    {
        $url = ($this->sandbox
            ? 'https://express.api.dhl.com/mydhlapi/test'
            : self::BASE_URL) . $path;

        $response = Http::withBasicAuth($this->apiKey, $this->apiSecret)
            ->withHeaders(['Content-Type' => 'application/json'])
            ->{strtolower($method)}($url, $params);

        if ($response->clientError()) {
            $error = $response->json();
            throw new DhlApiException(
                $error['detail'] ?? $error['title'] ?? 'DHL API error',
                $response->status()
            );
        }

        return $response->json();
    }
}

Sandbox credentials для тестирования: apiKey = demo-key, apiSecret = demo-secret — реальные тестовые ключи получают при регистрации в Developer Portal DHL.

Расчёт стоимости и сроков

public function getRates(
    array  $from,     // ['countryCode'=>'RU','cityName'=>'Moscow','postalCode'=>'101000']
    array  $to,       // ['countryCode'=>'DE','cityName'=>'Berlin','postalCode'=>'10115']
    float  $weightKg,
    array  $dimensions,
    string $plannedShipDate
): array {
    $data = $this->request('GET', '/rates', [
        'accountNumber'     => config('services.dhl.account_number'),
        'originCountryCode' => $from['countryCode'],
        'originCityName'    => $from['cityName'],
        'originPostalCode'  => $from['postalCode'],
        'destinationCountryCode' => $to['countryCode'],
        'destinationCityName'    => $to['cityName'],
        'destinationPostalCode'  => $to['postalCode'],
        'weight'            => $weightKg,
        'length'            => $dimensions['length'],
        'width'             => $dimensions['width'],
        'height'            => $dimensions['height'],
        'plannedShippingDateAndTime' => $plannedShipDate . 'T10:00:00 GMT+03:00',
        'isCustomsDeclarable' => true,
        'unitOfMeasurement'   => 'metric',
    ]);

    return collect($data['products'] ?? [])
        ->map(fn($p) => [
            'product_code' => $p['productCode'],
            'product_name' => $p['productName'],
            'currency'     => $p['totalPrice'][0]['priceCurrency'],
            'total_price'  => $p['totalPrice'][0]['price'],
            'delivery_time'=> $p['deliveryCapabilities']['deliveryTypeCode'],
            'delivery_date'=> $p['deliveryCapabilities']['estimatedDeliveryDateAndTime'] ?? null,
        ])
        ->toArray();
}

Основные продукт-коды: P — DHL Express Worldwide (самый востребованный), K — DHL Express 9:00, T — DHL Express 12:00, Y — DHL Express Envelope.

Создание отправления

public function createShipment(Order $order): array
{
    $payload = [
        'plannedShippingDateAndTime' => now()->addDay()->format('Y-m-d') . 'T10:00:00 GMT+03:00',
        'pickup' => [
            'isRequested' => false, // false = самостоятельная сдача на склад DHL
        ],
        'productCode' => $order->dhl_product_code ?? 'P',
        'accounts'    => [
            ['number' => config('services.dhl.account_number'), 'typeCode' => 'shipper'],
        ],
        'customerDetails' => [
            'shipperDetails' => [
                'postalAddress' => [
                    'postalCode'  => config('services.dhl.shipper_zip'),
                    'cityName'    => config('services.dhl.shipper_city'),
                    'countryCode' => 'RU',
                    'addressLine1'=> config('services.dhl.shipper_address'),
                ],
                'contactInformation' => [
                    'email'       => config('services.dhl.contact_email'),
                    'phone'       => config('services.dhl.contact_phone'),
                    'companyName' => config('services.dhl.company_name'),
                    'fullName'    => config('services.dhl.contact_name'),
                ],
            ],
            'receiverDetails' => [
                'postalAddress' => [
                    'postalCode'  => $order->shipping_zip,
                    'cityName'    => $order->shipping_city,
                    'countryCode' => $order->shipping_country_code,
                    'addressLine1'=> $order->shipping_address,
                ],
                'contactInformation' => [
                    'email'    => $order->recipient_email,
                    'phone'    => $order->recipient_phone,
                    'fullName' => $order->recipient_name,
                ],
            ],
        ],
        'content' => [
            'packages' => [[
                'weight'     => $order->total_weight_kg,
                'dimensions' => [
                    'length' => $order->package_length,
                    'width'  => $order->package_width,
                    'height' => $order->package_height,
                ],
            ]],
            'isCustomsDeclarable' => $order->is_international,
            'description' => 'E-commerce goods',
            'incoterm'    => 'DAP',
            'unitOfMeasurement' => 'metric',
            // Таможенная декларация для международных отправлений
            'exportDeclaration' => $order->is_international ? $this->buildExportDeclaration($order) : null,
        ],
    ];

    $response = $this->request('POST', '/shipments', $payload);

    return [
        'shipment_id'     => $response['shipmentTrackingNumber'],
        'shipment_number' => $response['shipmentDetails'][0]['shipmentTrackingNumber'],
        'label_pdf'       => base64_decode($response['documents'][0]['content'] ?? ''),
    ];
}

Таможенная декларация

Для международных отправлений обязательна:

private function buildExportDeclaration(Order $order): array
{
    return [
        'lineItems' => $order->items->map(fn($item, $i) => [
            'number'          => $i + 1,
            'description'     => $item->product->name_en, // на английском
            'price'           => $item->price,
            'priceCurrency'   => 'USD',
            'grossWeight'     => [
                'weight' => $item->product->weight_kg,
                'unitOfMeasurement' => 'kg',
            ],
            'quantity'        => [
                'value' => $item->quantity,
                'unitOfMeasurement' => 'PCS',
            ],
            'manufacturerCountry' => 'CN',
            'hsCode'          => $item->product->hs_code ?? '6109100000',
        ])->toArray(),
        'invoice' => [
            'number'      => 'INV-' . $order->id,
            'date'        => now()->format('Y-m-d'),
            'signedBy'    => config('services.dhl.contact_name'),
            'function'    => 'Seller',
            'customerReference' => (string)$order->id,
        ],
        'exportReason'    => 'PERMANENT',
        'exportReasonType'=> 'PERMANENT',
        'placeOfIncoterm' => 'Destination',
        'shipmentType'    => 'commercial',
    ];
}

Отслеживание

public function trackShipment(string $trackingNumber): array
{
    $response = $this->request('GET', '/tracking', [
        'trackingNumber' => $trackingNumber,
    ]);

    $shipment = $response['shipments'][0] ?? null;

    if (!$shipment) {
        return [];
    }

    return [
        'status'       => $shipment['status'],
        'description'  => $shipment['description'],
        'location'     => $shipment['location']['address']['cityName'] ?? '',
        'events'       => collect($shipment['events'])->map(fn($e) => [
            'timestamp'  => $e['timestamp'],
            'location'   => $e['location']['address']['cityName'] ?? '',
            'description'=> $e['description'],
        ])->toArray(),
        'estimated_delivery' => $shipment['estimatedTimeOfDelivery'] ?? null,
    ];
}

Вызов курьера

public function schedulePickup(
    string $date,
    string $timeFrom,
    string $timeTo,
    int    $parcelCount,
    float  $totalWeight
): string {
    $response = $this->request('POST', '/pickups', [
        'plannedPickupDateAndTime' => $date . 'T' . $timeFrom . ':00 GMT+03:00',
        'closeTime'   => $timeTo,
        'location'    => 'reception',
        'accounts'    => [['number' => config('services.dhl.account_number'), 'typeCode' => 'shipper']],
        'customerDetails' => ['shipperDetails' => [/* адрес */]],
        'shipmentDetails' => [[
            'packages'   => [['weight' => ['netValue' => $totalWeight, 'grossValue' => $totalWeight]]],
            'productCode'=> 'P',
            'isCustomsDeclarable' => false,
        ]],
        'pickupDetails' => [
            'pickupRequestorDetails' => [
                'fullName'=> config('services.dhl.contact_name'),
                'phone'   => config('services.dhl.contact_phone'),
            ],
        ],
    ]);

    return $response['dispatchConfirmationNumber'];
}

Ограничения и ошибки

DHL строго проверяет адреса получателей. Неточный или несуществующий почтовый индекс вернёт ошибку ещё на этапе создания отправления, а не после сдачи посылки. Для ряда стран обязателен точный штат/провинция (stateOrProvince). Максимальный вес одного места — 70 кг, максимальный размер стороны — 300 см.

Сроки

Интеграция DHL Express для интернет-магазина — расчёт, создание отправлений, отслеживание — 5–7 дней. Добавление таможенных деклараций для международных отправлений — ещё 2–3 дня. Полное тестирование в sandbox среде перед боевым запуском — обязательно.