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

Integracja API logistycznego SUUS

Pierwsza otwartoźródłowa biblioteka PHP do integracji z API spedycyjnym SUUS (Röhlig Logistics): tworzenie przesyłek, walidacja wstępna, śledzenie statusów, pobieranie etykiet i dokumentów oraz kalendarze dni roboczych dziewięciu krajów, z pełnym typowaniem.

PHP 8.2+ SOAP API Logistics PHPStan level 8 Packagist Open Source

Czym jest suus-php?

SUUS (obecnie część Röhlig Logistics) to spedytor i kurier działający w Europie Środkowo-Wschodniej, powszechnie wykorzystywany do przesyłek B2B - paczkowych i paletowych - w Polsce, Niemczech, Austrii, Szwajcarii i na rynkach sąsiednich. Każda platforma nadająca towar przez SUUS musi porozumieć się z ich API.

Kłopot w tym, że SUUS udostępnia stary interfejs SOAP 1.1 w wariancie RPC/encoded, ze skąpą dokumentacją, nieoczywistymi kodami statusów i bez oficjalnego SDK dla PHP - wbudowany w PHP SoapClient nie potrafi się z nim nawet dogadać. suus-php to pierwszy otwartoźródłowy klient PHP, który zamyka to API w czystym, nowoczesnym interfejsie: typowane DTO dla każdego żądania i odpowiedzi, znormalizowany enum statusów, kalendarze dni roboczych dla dziewięciu krajów, walidacja wstępna wyłapująca odrzucenia zanim opuszczą Twój serwer, i pełne typowanie na poziomie PHPStan 8.

Biblioteka jest dostępna na Packagist na licencji Apache 2.0 i instaluje się jednym poleceniem Composera. Działające utworzenie przesyłki mieści się w mniej niż dziesięciu liniach kodu.

Co obejmuje API

Wszystkie operacje SUUS WebApi (WS PK 1.0), każda dostępna zarówno po numerze listu przewozowego SUUS, jak i po Twoim własnym numerze referencyjnym - bo integracja zwykle trzyma swój numer, a nie ich.

createShipment()
Rejestruje zlecenie wysyłki (SUUS addOrder) i zwraca numer listu przewozowego, Twoją referencję oraz gotowy link do śledzenia.
validate()
Uruchamia pełen zestaw reguł lokalnie, bez odpytywania sieci, i zwraca typowane obiekty ValidationError - kod, pole i komunikat - więc własny interfejs może pokazać problem, zanim cokolwiek zostanie wysłane.
fetchStatus()
fetchStatusByReference()
Pełna historia zdarzeń (SUUS getEvents) wraz ze znormalizowanym statusem, z każdym znacznikiem czasu sparsowanym do prawdziwego DateTimeImmutable.
fetchDocument()
fetchDocumentByReference()
Etykiety (A4 albo termiczna A6), zlecenie wysyłki i lista załadunkowa jako surowe bajty PDF; opcjonalnie zawężone do wybranych paczek przesyłki wielopaczkowej.
fetchLabel()
Skrót po standardową etykietę A4.
fetchLoadingList()
Zbiorcza lista załadunkowa - jedyny dokument pobierany po numerze listu głównego, a nie po przesyłce.
getColliNumbers()
getColliNumbersByReference()
Numery poszczególnych paczek (colli) przesyłki wielopaczkowej, używane do pobrania pojedynczej etykiety.

Co robi

Tworzenie przesyłek

Zlecenia buduje się z typowanych obiektów PHP: nadawca, odbiorca, paczki, wymiary, incoterms, grupa kosztowa, zadeklarowany fracht. Wszystko jest walidowane lokalnie, zanim trafi do SUUS, więc błąd pojawia się we własnym stack trace, a nie jako nieczytelny kod błędu.

Śledzenie statusu

Zdarzenia są tłumaczone na znormalizowany enum ShipmentStatus - Created, InTransit, Delivered, Cancelled, Failed - zamiast kryptycznych kodów natywnych w rodzaju ROZF, WTRF czy ZWRON. Surowy kod pozostaje dostępny, gdy jest potrzebny.

Etykiety i dokumenty

Etykiety wracają jako surowe bajty PDF w formacie A4 lub termicznym A6, obok zleceń wysyłki i list załadunkowych. Dla przesyłki wielopaczkowej można pobrać etykietę jednej konkretnej paczki zamiast całego kompletu.

Usługi dodatkowe

Dziewięć typowanych obiektów usług: pobranie, ubezpieczenie, awizacja e-mail i SMS, winda, paleciak, wniesienie oraz dokumenty zwrotne - każdy z ograniczeniami trasy i typu zlecenia pilnowanymi lokalnie.

Kalendarze dni roboczych

SUUS wymaga dwóch dni roboczych wyprzedzenia, a "dzień roboczy" zależy od tego, gdzie towar jest odbierany. Biblioteka zawiera kalendarze wszystkich dziewięciu krajów obsługiwanych przez SUUS i sama dobiera właściwy na podstawie adresu nadawcy.

Tryb sandbox

Jeden nazwany konstruktor przełącza klienta na środowisko testowe SUUS. Nie powstają żadne prawdziwe przesyłki, a pierwsze tygodnie prac integracyjnych nie wymagają danych produkcyjnych.

PHPStan poziom 8

Każda metoda, DTO i enum są w pełni otypowane. IDE dokładnie wie, z czym pracuje - żadnych magicznych tablic, żadnego zgadywania, jakie pola wrócą w odpowiedzi.

Tryb debug

Jedna flaga dołącza do każdego wyjątku dokładny XML zwrócony przez SUUS i loguje pełny raport - komunikat, surową odpowiedź i stack trace - przez wstrzyknięty logger PSR-3. To właśnie zamienia gołe BTN0001 w coś, z czym da się cokolwiek zrobić.

Ładunek, opakowania i limity

Trzynaście symboli opakowań SUUS jest wystawionych jako enum - palety EUR, jednorazowe, przemysłowe, DHP i CHEP, kartony, skrzynie, rolki, pojemniki DPPL, gabaryty AGD, wiązki, hoboki oraz uniwersalny typ przeładunkowy. Każda paczka ma wagę i opcjonalne wymiary, a obsługa opakowań zwrotnych i piętrowalnych odwzorowuje regułę wymuszaną przez SUUS (zwrotna paleta EUR musi być oznaczona jako piętrowalna).

Limity przewoźnika sprawdzane są przed wysłaniem żądania, a nie po odrzuceniu:

  • 126 kg na paczkę i 800 kg na zlecenie
  • 124 paczki na przesyłkę
  • wymiary maksymalne 240 x 120 x 220 cm
  • minimalna wysokość 20 cm dla palet EUR
  • opakowania zwrotne i piętrowalne wyłącznie w ruchu krajowym

Usługi dodatkowe

Usługi dodatkowe przekazuje się jako typowane obiekty, automatycznie serializowane do symboli usług SUUS. Dostępność zależy od trasy i typu zlecenia, a biblioteka odrzuca niedozwoloną kombinację lokalnie, zamiast pozwolić SUUS-owi odpowiedzieć kodem, który trzeba potem odszukiwać.

Pobranie (COD)
Do 15 000 PLN, krajowo i międzynarodowo, B2B i B2C.
Ubezpieczenie
Wartość deklarowana wraz z rodzajem towaru (standardowy, farmaceutyczny, kontrolowana temperatura), kosztami dodatkowymi oraz klauzulami strajkową i wojenną. Obowiązkowe oświadczenie SUUS, że towar nie należy do grupy wyłączonej, wysyłane jest zawsze, bo jego pominięcie to gwarantowane odrzucenie.
Powiadomienia e-mail
Awizacja dla nadawcy, odbiorcy albo obu stron; każda powiadamiana strona musi mieć adres e-mail przy swoim adresie.
Awizacja SMS
Wyłącznie krajowe B2C, wymaga numeru komórkowego przy adresie odbiorcy.
Winda
Rozładunek windą dla towaru do 750 kg.
Paleciak
Paleciak dostępny przy dostawie.
Wniesienie
Wniesienie towaru do środka; wyłącznie krajowe B2C.
Dokumenty zwrotne
Podpisane dokumenty odsyłane do nadawcy, w osobnych wariantach krajowym i międzynarodowym, z numerem, znacznikiem i typem dokumentu.

Trasy międzynarodowe i incoterms

Przesyłka jest międzynarodowa, gdy tylko którakolwiek ze stron znajduje się poza Polską - jedynie trasa z Polski do Polski liczy się jako krajowa, a niemiecki nadawca dostarczający do niemieckiego odbiorcy to wciąż międzynarodowy produkt SUUS. Ten jeden fakt pociąga za sobą zestaw reguł, które łatwo poznać dopiero po odrzuceniu zlecenia, więc biblioteka pilnuje ich wszystkich przed wywołaniem API:

  • incoterms są obowiązkowe - obsługiwanych jest dziesięć, od EXW po DDP
  • typ zlecenia musi być B2B; B2C nie jest dostępne międzynarodowo
  • opakowania zwrotne i piętrowalne są odrzucane
  • krajowe usługi B2C (awizacja SMS, wniesienie) są niedostępne
  • zadeklarowany fracht i waluta mogą zostać wysłane, ale wyłącznie razem

Każdą z tych reguł można rozluźnić. ValidationPolicy wyłącza wymuszanie zasad międzynarodowych tam, gdzie pozwala na to umowa, a RouteClassifier pozwala samodzielnie zdefiniować, które trasy biblioteka w ogóle traktuje jako międzynarodowe - co bywa przydatne przy lokalnej umowie krajowej.

Walidacja przed wywołaniem sieciowym

Najwięcej bólu w integracji z SUUS sprawia nie napisanie żądania, lecz ustalenie, dlaczego zostało odrzucone. Biblioteka przenosi to ustalanie na Twoją stronę łącza: validate() uruchamia dokładnie te same kontrole co createShipment(), nie dotykając sieci, i zwraca uporządkowane błędy, które tam gdzie to możliwe używają prawdziwych kodów SUUS.

foreach ($client->validate($order) as $error) {
    echo "[{$error->code}] {$error->field}: {$error}\n";
    // [PRJ00372] packages[0].returnable: opakowanie zwrotne niedostepne na trasie miedzynarodowej
}

Te same typowane błędy niesie wyjątek rzucany przez createShipment(), więc formularz i zadanie w tle mogą korzystać z jednego komponentu wyświetlającego błędy.

Szybki start

composer require very-code-com/suus-php
$client = SuusClient::sandbox('ws_yourlogin', 'your_password');

$result = $client->createShipment(new ShipmentOrder(
    reference: 'ORDER-2026-001',
    sender:    new Address('Sender GmbH', 'Musterstr.', '1', '10115', 'Berlin', 'DE', phone: '+4930123'),
    receiver:  new Address('Odbiorca Sp. z o.o.', 'Marszalkowska', '100', '00-026', 'Warszawa', 'PL', phone: '+48600000'),
    packages:  [new Package(PackageSymbol::EUR, weightKg: 120.0)],
    incoterms: Incoterm::DAP,
    orderType: OrderType::B2B,
));

echo $result->shipmentNo;   // OPLKRI2600895
echo $result->trackingUrl;  // https://portal.suus.com/order-details/OPLKRI2600895

file_put_contents('label.pdf', $client->fetchLabel($result->shipmentNo));

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

Zbudowane tak, żeby działało dalej

Sześć typów wyjątków rozdziela przypadki, które chce się obsłużyć inaczej: walidacja lokalna, odrzucone dane logowania, zduplikowana referencja, błąd biznesowy SUUS, awaria transportu i nieparsowalna odpowiedź. Każdy z nich udostępnia surową odpowiedź i pełny raport diagnostyczny.

Continuous integration uruchamia testy jednostkowe na PHP 8.2, 8.3 i 8.4 wraz z PHPStan na poziomie 8 przy każdym pushu, a osobny nocny workflow przepuszcza cały zestaw integracyjny przez żywy sandbox SUUS - tworząc prawdziwe zlecenia testowe i odczytując z powrotem ich zdarzenia, numery colli i dokumenty. To właśnie ten drugi zestaw sprawił, że biblioteka dokumentuje kwiatki SUUS-a, na których inne klienty się wykładają: literówkę lenghtCm w schemacie, zamianę przestrzeni nazw w odpowiedziach oraz fakt, że PRJ000001 co najmniej równie często znaczy "nie potrafiłem odczytać żądania", co "nie ma takiego zlecenia".

Dla kogo?

Dla programistów PHP podłączających platformę - sklep internetowy, ERP, system magazynowy albo autorski proces logistyczny - do sieci przewozowej SUUS. Zespołowi nadającemu ładunki B2B przez SUUS z backendem w PHP biblioteka oszczędza tygodnie inżynierii wstecznej interfejsu SOAP i zastępuje ją paczką Composera, którą da się przeczytać na code review.