Интеграция службы доставки DPD на сайт
DPD — международный логистический оператор с разветвлённой сетью в России и странах СНГ. API предоставляется через SOAP-сервисы, что несколько архаично по меркам современных REST-интеграций, но вполне работоспособно. Для PHP есть официальный SDK, который скрывает сложность SOAP под удобным фасадом.
Подключение и инициализация
// composer require dpd-ru/dpd-php-api-client
use DPD\DPDClient;
$client = new DPDClient(
username: config('services.dpd.username'),
password: config('services.dpd.password'),
clientNumber: config('services.dpd.client_number'),
test: app()->environment('production') === false
);
Тестовая среда (test: true) работает на https://test.dpd.ru. Учётные данные для теста запрашиваются у менеджера DPD отдельно от боевых.
Расчёт стоимости
DPD предоставляет метод getDPDOrderCost2 для расметки тарифов:
public function calculateDelivery(
string $fromCityId, // код города DPD (не ISO)
string $toCityId,
float $weightKg,
array $dimensions,
float $declaredValue = 0
): array {
$response = $this->client->getDPDOrderCost2([
'pickup' => [
'CityID' => $fromCityId,
'CountryCode' => 'RU',
],
'delivery' => [
'CityID' => $toCityId,
'CountryCode' => 'RU',
],
'selfPickup' => false,
'selfDelivery' => false,
'weight' => $weightKg,
'volume' => ($dimensions['length'] * $dimensions['width'] * $dimensions['height']) / 1000000, // м³
'serviceCode' => 'ALL', // получить все доступные сервисы
'declaredValue'=> $declaredValue,
]);
return collect($response->return ?? [])
->filter(fn($r) => $r->result === 'OK')
->map(fn($r) => [
'service_code' => $r->serviceCode,
'service_name' => $r->serviceName,
'cost' => (float)$r->cost,
'min_days' => (int)$r->days,
'max_days' => (int)$r->days + 1,
])
->values()
->toArray();
}
Коды сервисов: BZP — бизнес-посылка (дверь-дверь), PCL — посылка до терминала, MAX — максималка (крупные грузы), EXPRESS — экспресс-доставка.
Поиск городов
DPD использует собственные ID городов. Поиск по названию:
public function findCity(string $cityName, string $countryCode = 'RU'): array
{
$response = $this->client->getCitiesCashPay([
'countryCode' => $countryCode,
'regionCode' => null,
'cityName' => $cityName,
]);
return collect($response->return ?? [])
->map(fn($c) => [
'id' => $c->cityId,
'name' => $c->cityName,
'region' => $c->regionName,
'abbreviation'=> $c->regionCode,
])
->toArray();
}
Рекомендуется кешировать справочник городов локально (синхронизировать раз в неделю), так как запросы к getCitiesCashPay работают медленно.
Пункты выдачи (терминалы)
public function getTerminals(?string $cityId = null): array
{
$params = [];
if ($cityId) {
$params['cityId'] = $cityId;
}
$response = $this->client->getTerminalsSelfDelivery2($params);
return collect($response->return ?? [])
->map(fn($t) => [
'id' => $t->terminalCode,
'name' => $t->terminalName,
'address' => $t->address,
'city' => $t->city,
'lat' => (float)($t->geoCoordinates->latitude ?? 0),
'lng' => (float)($t->geoCoordinates->longitude ?? 0),
'work_time' => $this->formatWorkTime($t->schedule ?? []),
'phone' => $t->phone ?? null,
'email' => $t->email ?? null,
'cash_allowed' => (bool)($t->payCash ?? false),
'card_allowed' => (bool)($t->payCard ?? false),
])
->toArray();
}
Создание заказа
public function createOrder(Order $order): array
{
$response = $this->client->createOrder([
'header' => [
'datePickup' => now()->addDay()->format('Y-m-d') . 'T10:00:00',
'senderAddress' => [
'name' => config('services.dpd.sender_name'),
'countryCode' => 'RU',
'zipCode' => config('services.dpd.sender_zip'),
'city' => config('services.dpd.sender_city'),
'street' => config('services.dpd.sender_street'),
'house' => config('services.dpd.sender_house'),
'contactFio' => config('services.dpd.contact_name'),
'contactPhone'=> config('services.dpd.contact_phone'),
],
'pickupTimePeriod'=> '10:00-18:00',
'senderPhone' => config('services.dpd.contact_phone'),
],
'order' => [[
'orderNum' => (string)$order->id,
'serviceCode' => $order->dpd_service_code,
'serviceVariant' => 'ДД', // ДД=дверь-дверь, ДТ=дверь-терминал
'cargoNumPack' => 1,
'cargoWeight' => $order->total_weight_kg,
'cargoVolume' => $this->calculateVolume($order),
'cargoRegistered' => false,
'cargoCategory' => 'Товары народного потребления',
'receiverAddress' => [
'name' => $order->recipient_name,
'countryCode' => 'RU',
'zipCode' => $order->shipping_zip,
'city' => $order->shipping_city,
'street' => $order->shipping_street,
'house' => $order->shipping_house,
'flat' => $order->shipping_flat ?? '',
'contactFio' => $order->recipient_name,
'contactPhone'=> $order->recipient_phone,
'contactEmail'=> $order->recipient_email,
],
'terminalCode' => $order->dpd_terminal_code ?? null,
]],
]);
$result = $response->return[0] ?? null;
if (!$result || $result->status !== 'OK') {
throw new DpdOrderException(
'DPD order creation failed: ' . ($result->errorMessage ?? 'unknown error')
);
}
return [
'order_num' => $result->orderNum,
'dpd_order_num' => $result->orderNumberInternal,
];
}
Отслеживание
public function trackParcel(string $dpdOrderNum): array
{
$response = $this->client->getStatesByClient([
'clientOrderNr' => $dpdOrderNum,
]);
return collect($response->return ?? [])
->map(fn($s) => [
'date' => $s->transitionTime,
'status' => $s->newState,
'city' => $s->terminalCityName ?? '',
'comment' => $s->comment ?? '',
])
->toArray();
}
Печать накладных
DPD возвращает этикетки в формате PDF или ZPL (для термопринтеров):
public function printLabel(string $dpdOrderNum, string $format = 'PDF'): string
{
$response = $this->client->createLabelFile([
'OrderRanges' => [
'clientOrderNr' => $dpdOrderNum,
],
'fileFormat' => $format, // PDF, ZPL
'pageSize' => 'A5', // A4, A5, A6
'printMode' => 'TWO_SIDED',
]);
return base64_decode($response->return->fileContent);
}
Вызов курьера для забора
public function createPickupRequest(
string $date, // 'Y-m-d'
string $timeFrom, // '10:00'
string $timeTo, // '18:00'
int $parcelCount,
float $totalWeight
): string {
$response = $this->client->addPickupAsync([
'pickupDate' => $date . 'T00:00:00',
'readyTime' => $timeFrom,
'closeTime' => $timeTo,
'addInfo' => "Посылок: $parcelCount, вес: {$totalWeight} кг",
'senderAddress' => [/* адрес склада */],
]);
return $response->return->pickupRequestNumber ?? '';
}
Типичные проблемы интеграции
SOAP-ответы DPD иногда возвращают ошибки не через HTTP-статус, а в теле с status !== 'OK'. Необходима проверка каждого объекта ответа. Коды городов — не КЛАДР и не ФИАС, нужен собственный маппинг или поиск через их API при каждом запросе.
Сроки
Базовая интеграция — расчёт, ПВЗ, создание заказов — 5–7 дней. SOAP-специфика и тестирование добавляют 1–2 дня по сравнению с REST-шлюзами.







