very-code_
← Powrót na stronę główną

Integracja API logistycznego DPD DE

Otwartoźródłowy klient PHP dla DPD Cloud Service Webservice (DPD Deutschland): nadawanie przesyłek i etykiety, walidacja lokalna i po stronie DPD, dwa modele śledzenia, wyszukiwarka ParcelShop i zasady odbiorów, przez SOAP i REST.

PHP 8.2+ SOAP + REST DPD Cloud Service Logistics PHPStan level 8 Packagist Open Source

Czym jest dpd-de-php?

DPD to jedna z największych sieci kurierskich w Europie, a niemiecki DPD udostępnia swoją platformę nadawczą przez DPD Cloud Service Webservice - interfejs stojący za drukiem etykiet, śledzeniem paczek, wyszukiwaniem punktów ParcelShop i harmonogramem odbiorów dla niemieckich kont firmowych.

dpd-de-php to otwartoźródłowy klient PHP dla tej usługi. Obsługuje wszystkie pięć operacji przez oba transporty oferowane przez DPD - SOAP i REST - za jednym, identycznym API; waliduje zlecenia lokalnie według własnego załącznika z kodami błędów DPD, zanim cokolwiek zostanie wysłane; i jest otypowany do poziomu PHPStan 8, od danych logowania po kamienie milowe śledzenia.

W integracji z DPD łatwo nie docenić nie tego, co działa od razu, lecz dwóch par danych uwierzytelniających, dwóch zupełnie różnych modeli śledzenia, pól, które WSDL nazywa opcjonalnymi, a API odrzuca jako brakujące, oraz danych do sandboxa, których nie ma tam, gdzie wskazuje oficjalna dokumentacja. Wszystko to jest w paczce obsłużone i opisane.

Co obejmuje API

Wszystkie operacje DPD Cloud Service, każda dostępna przez SOAP (domyślnie, zgodnie z żywym WSDL) oraz przez REST.

createShipment()
createShipments()
Nadaje jedną paczkę albo do 30 w jednym wywołaniu i zwraca numery paczek razem z PDF-em etykiety - już zdekodowanym z Base64 i gotowym do zapisu na dysk.
checkOrderData()
Suchy przebieg po stronie DPD: przewoźnik waliduje dokładnie te dane zlecenia, nie tworząc przesyłki ani nie zużywając numeru paczki.
validateLocally()
Te same kontrole pól i reguł biznesowych, które klient wykonuje przed każdym nadaniem, dostępne na żądanie i całkowicie bez ruchu sieciowego.
fetchOrderStatus()
Śledzenie strukturalne (Parcel Life Cycle 3.1): informacje o zleceniu, adres doręczenia, ostatni status i pięć nazwanych kamieni milowych - nadanie, w drodze, oddział doręczający, załadunek, doręczono.
fetchParcelLifeCycle()
Starszy model śledzenia nastawiony na prezentację (Parcel Life Cycle 2.0): gotowe bloki tekstu z flagami pogrubienia i akapitu, przeznaczone do wyrenderowania wprost na stronie śledzenia.
findParcelShops()
Wyszukiwanie punktów ParcelShop i automatów po adresie albo współrzędnych, wraz z godzinami otwarcia, dniami wolnymi, odległością, oferowanymi usługami i metodą isParcelLocker().
fetchZipCodeRules()
Zasady odbioru dla adresu Twojego konta: dni bez odbioru jako sparsowane daty, godziny graniczne dla Express i Classic, oddział odbierający i region.
lastSystemInformation()
Komunikaty serwisowe DPD - planowane przerwy, nadchodzące zmiany w API - dołączane do każdej odpowiedzi i logowane na poziomie notice.

Co robi

Przesyłki i etykiety

Nadanie zwraca numer paczki i PDF etykiety w tej samej odpowiedzi. Rozmiar etykiety, pozycja startowa i format wydruku są konfigurowalne per zlecenie przez typowane ustawienia.

Osiemnaście produktów

Classic, Predict, zwroty, doręczenie i zwrot przez ParcelShop, cała rodzina Express od 8:30 do 18:00 wraz z wariantami sobotnimi, Express International oraz wycofane produkty za pobraniem zachowane dla kompletności - wszystko jako jeden enum.

Dwa modele śledzenia

DPD utrzymuje dwie niezależne usługi śledzenia i żadna nie zastępuje drugiej. Zaimplementowane są obie: strukturalna do maszyn stanów i zapisu kamieni milowych, tekstowa do wyrenderowania strony dla klienta.

Wyszukiwarka punktów

Punkty odbioru po adresie albo współrzędnych, z danymi potrzebnymi, żeby je faktycznie pokazać: odległość, godziny otwarcia, zamknięcia świąteczne i usługi dostępne w danym punkcie.

Walidacja przed wysyłką

Długości pól, limity wagi i reguły biznesowe DPD sprawdzane przed wywołaniem, liczone w znakach, a nie w bajtach - dokładnie tak, jak liczy je DPD - więc 35-znakowy ciąg z umlautami nie zostanie fałszywie odrzucony.

SOAP albo REST, jedno API

Transport przełącza się argumentem konstruktora. Kontraktu REST nie ma w PDF-ie DPD w ogóle; został odtworzony z przykładów kodu w portalu, razem z dwiema regułami budowy URL-i, których pominięcie po cichu kończy się kodem 404.

Limit wywołań jako osobny błąd

DPD egzekwuje limit wywołań na konto z dziesięciominutową karencją. Ma on własny typ wyjątku z metodą retryAfterSeconds() i celowo nie jest błędem uwierzytelniania - właściwą reakcją jest odczekanie, a nie szukanie nowych danych logowania.

PHPStan poziom 8

Typowane DTO, enumy i udokumentowana hierarchia wyjątków od początku do końca, plus interfejs transportu, dzięki któremu cały klient da się przetestować bez ani jednego wywołania sieciowego.

Walidacja odwzorowująca DPD

Każde nadanie jest sprawdzane lokalnie wobec ograniczeń z załącznika z kodami błędów w dokumentacji DPD, a każda kontrola wskazuje błąd, któremu zapobiega. Reguły pól obejmują wagę (0-31,5 kg), limity 35 znaków dla zawartości i referencji oraz zasady adresowe DPD dla firmy, nazwiska, formy grzecznościowej, ulicy, numeru domu, miasta, kodu pocztowego, telefonu, e-maila i regionu.

Reguły biznesowe to te, które przy pierwszym spotkaniu potrafią zająć pół dnia:

  • Content, YourInternalID i Reference1w praktyce obowiązkowe, choć WSDL oznacza je jako opcjonalne
  • region jest wymagany dla USA i Kanady, a zabroniony wszędzie indziej - rozpoznawany we wszystkich zapisach akceptowanych przez DPD, od US po United States
  • produkty Predict wymagają adresu e-mail albo numeru telefonu do powiadomienia o doręczeniu
  • Classic Return wymaga numeru telefonu i nie da się go wysłać w paczce zbiorczej
  • doręczenie do punktu wymaga prawdziwego identyfikatora ParcelShopu - wyszukanego przez findParcelShops()
  • produkty Express 8:30-18:00 są wyłącznie krajowe dla Niemiec; za granicę służy Express International
  • jedna partia mieści maksymalnie 30 zleceń, a DPD Cloud Service nie zna przesyłek wielopaczkowych: każda fizyczna paczka to osobna pozycja

Walidacja lokalna jest najlepszym możliwym odwzorowaniem, a nie zamiennikiem walidacji DPD. Nie wie, czy adres istnieje ani czy produkt jest dostępny na danej relacji - od tego jest checkOrderData() i dlatego w bibliotece są obie.

Dane logowania, środowiska i sandbox

DPD Cloud Service uwierzytelnia dwiema parami danych: dane partnera identyfikują samą integrację, a dane użytkownika - konto klienta DPD. Obie wydawane są osobno dla każdego środowiska, a para produkcyjna użyta wobec systemu testowego kończy się dokładnie tak samo jak nieprawidłowa.

Zdobycie dostępu do sandboxa to moment, w którym oficjalna dokumentacja aktywnie wprowadza w błąd. Strona portalu, na którą wskazuje zarówno PDF, jak i wsparcie DPD, wydaje dane do innego API - DPD Web Connect - którego formatu nie da się przełożyć na dane Cloud. Prawdziwe konto sandboxowe Cloud jest wypełnione w przykładach kodu w portalu, na stronie nieosiągalnej zwykłą nawigacją bez konkretnego parametru w adresie. README biblioteki opisuje tę drogę, żeby nikt nie musiał tracić na to dnia po raz drugi.

composer require very-code-com/dpd-de-php
$client = DpdCloudClient::sandbox('DPD Sandbox', $partnerToken, $userId, $userToken);

$result = $client->createShipment(new OrderItem(
    shipAddress: new Address(
        name: 'Max Mustermann', street: 'Musterstr.', houseNo: '1',
        zipCode: '12345', city: 'Berlin', country: 'DE',
    ),
    parcelShopId: 0,
    parcel: new Parcel(
        ShipService::Classic,
        weightKg: 2.5,
        content: 'Testware',
        yourInternalId: 'ORDER-1',
        reference1: 'ORDER-1',
    ),
));

echo $result->firstParcelNo();                       // 01234567890123
file_put_contents('label.pdf', $result->labelPdf);   // juz zdekodowany

Konfiguracja może równie dobrze pochodzić ze zmiennych środowiskowych albo z tablicy konfiguracyjnej frameworka, a klient przyjmuje własny transport i logger PSR-3 na potrzeby wstrzykiwania zależności i testów.

Zbudowane tak, żeby działało dalej

Sześć typów wyjątków odpowiada sześciu różnym reakcjom: walidacja lokalna, odrzucone dane logowania, limit wywołań z własną podpowiedzią czasu ponowienia, błąd biznesowy DPD niosący strukturalne identyfikatory i kody, awaria transportu oraz nieparsowalna odpowiedź. Każdy udostępnia surową odpowiedź i pełny raport diagnostyczny, gdy włączony jest tryb debug.

Continuous integration uruchamia testy jednostkowe na PHP 8.2, 8.3 i 8.4 z PHPStan na poziomie 8 przy każdym pushu i sprawdza składnię każdego przykładu. Osobny nocny workflow przepuszcza wszystkie pięć operacji przez oba transporty na żywym systemie testowym DPD, łącznie z prawdziwym nadaniem wydającym etykietę. Celowo nie jest częścią bramki na pull requestach: limit wywołań na konto zostałby przekroczony w obrębie jednego przebiegu macierzy.

Jedno ograniczenie DPD warto znać, zanim zaprojektuje się wokół niego system: nie istnieje operacja ponownego pobrania etykiety po numerze paczki. PDF zwrócony przy nadaniu to jedyny egzemplarz, jaki API kiedykolwiek wyda - więc trzeba go zapisać. Biblioteka to dokumentuje, zamiast udawać, że istnieje obejście.

Dla kogo?

Dla zespołów PHP nadających paczki przez niemieckiego DPD - sklepu drukującego etykiety przy pakowaniu, systemu ERP zamawiającego odbiory hurtowo, marketplace'u pokazującego klientom, gdzie jest paczka, albo portalu zwrotów podpowiadającego punkty nadania. Zamiast ręcznie sklejanej koperty SOAP i folderu notatek z prób i błędów, integracja staje się otypowaną paczką Composera z opisanymi kwiatkami przewoźnika.