DPD delivery service integration
DPD is international logistics operator with network across Russia and CIS. API provided via SOAP services, somewhat archaic by modern REST standards, but functional. Official SDK for PHP hides SOAP complexity.
Connection and initialization
use DPD\DPDClient;
$client = new DPDClient(
username: config('services.dpd.username'),
password: config('services.dpd.password'),
clientNumber: config('services.dpd.client_number'),
test: !app()->isProduction()
);
Test environment works on https://test.dpd.ru. Credentials for testing requested separately from production.
Rate calculation
public function calculateDelivery(
string $fromCityId,
string $toCityId,
float $weightKg,
array $dimensions
): array {
$response = $this->client->getDPDOrderCost2([
'pickup' => ['CityID' => $fromCityId, 'CountryCode' => 'RU'],
'delivery' => ['CityID' => $toCityId, 'CountryCode' => 'RU'],
'selfPickup' => false,
'selfDelivery' => false,
'weight' => $weightKg,
'serviceCode' => 'ALL',
]);
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,
])
->toArray();
}
Service codes: BZP (door-door), PCL (to terminal), MAX (large cargo), EXPRESS.
Finding cities
public function findCity(string $cityName): array
{
$response = $this->client->getCitiesCashPay([
'cityName' => $cityName,
]);
return collect($response->return ?? [])
->map(fn($c) => [
'id' => $c->cityId,
'name' => $c->cityName,
])->toArray();
}
Cache cities locally — requests are slow.
Terminals (pickup points)
public function getTerminals(?string $cityId = null): array
{
$response = $this->client->getTerminalsSelfDelivery2(
$cityId ? ['cityId' => $cityId] : []
);
return collect($response->return ?? [])
->map(fn($t) => [
'id' => $t->terminalCode,
'name' => $t->terminalName,
'address' => $t->address,
'lat' => (float)($t->geoCoordinates->latitude ?? 0),
'lng' => (float)($t->geoCoordinates->longitude ?? 0),
'phone' => $t->phone ?? null,
])->toArray();
}
Order creation
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'),
'city' => config('services.dpd.sender_city'),
],
'senderPhone' => config('services.dpd.contact_phone'),
],
'order' => [[
'orderNum' => (string)$order->id,
'serviceCode' => $order->dpd_service_code,
'serviceVariant' => 'ДД',
'cargoWeight' => $order->total_weight_kg,
'receiverAddress' => [
'name' => $order->recipient_name,
'city' => $order->shipping_city,
'contactPhone'=> $order->recipient_phone,
],
]],
]);
$result = $response->return[0] ?? null;
if (!$result || $result->status !== 'OK') {
throw new DpdOrderException('DPD order creation failed');
}
return [
'order_num' => $result->orderNum,
'dpd_order_num' => $result->orderNumberInternal,
];
}
Tracking
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 ?? '',
])->toArray();
}
Label printing
public function printLabel(string $dpdOrderNum, string $format = 'PDF'): string
{
$response = $this->client->createLabelFile([
'OrderRanges' => ['clientOrderNr' => $dpdOrderNum],
'fileFormat' => $format,
'pageSize' => 'A5',
]);
return base64_decode($response->return->fileContent);
}
Common issues
Responses sometimes return errors in body with status !== 'OK' instead of HTTP status. Need to check each response. City codes not KLADR/FIAS — own mapping needed.
Timeline
Basic integration — 5–7 days. SOAP complexity adds 1–2 days vs REST gateways.







