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.
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.
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.
addOrder) i zwraca numer listu przewozowego, Twoją referencję oraz gotowy link do śledzenia.ValidationError - kod, pole i komunikat - więc własny interfejs może pokazać problem, zanim cokolwiek zostanie wysłane.getEvents) wraz ze znormalizowanym statusem, z każdym znacznikiem czasu sparsowanym do prawdziwego DateTimeImmutable.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.
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 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.
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.
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.
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.
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.
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ć.
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:
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ć.
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:
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.
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.
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.
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 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.