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

Integracja API logistycznego Cargoboard

Otwartoźródłowy klient PHP dla API Cargoboard: wiążące ceny frachtu, rezerwacje, etykiety i potwierdzenia, śledzenie w czasie rzeczywistym z webhookami, faktury oraz dane ADR, od drobnicy po całe pojazdy w 32 krajach Europy.

PHP 8.2+ REST API Freight / LTL Webhooks PHPStan level 8 Packagist Open Source

Czym jest cargoboard-php?

Cargoboard to cyfrowy spedytor: zamiast wysyłać zapytanie ofertowe i czekać, aż ktoś odpowie, przesyłasz dane ładunku i w tej samej odpowiedzi dostajesz wiążącą cenę - a potem rezerwujesz dokładnie tę cenę. Obsługiwane są przesyłki drobnicowe, ładunki częściowe rozliczane metrami ładunkowymi, całopojazdowe, całe pojazdy oraz paczki, w 32 krajach Europy.

cargoboard-php to otwartoźródłowy klient PHP pokrywający wszystkie endpointy publicznego API, a do tego payload webhooka Track & Trace oraz linki do śledzenia dla klienta końcowego. Jest otypowany do poziomu PHPStan 8, waliduje żądanie według udokumentowanych reguł Cargoboard, zanim je wyśle, i został sprawdzony od początku do końca na żywym sandboxie: wycena, rezerwacja, odczyt zlecenia, śledzenie, PDF-y etykiet i potwierdzenia oraz anulowanie.

Wersja 1.1 dołożyła to, czego można się nauczyć wyłącznie prowadząc integrację na produkcji - i dokładnie stamtąd pochodzi: z uwag integratora pracującego na żywym koncie, przy czym każde stwierdzenie zostało ponownie zweryfikowane wobec definicji OpenAPI Cargoboard, zanim trafiło do kodu.

Co obejmuje API

Wszystkie publikowane endpointy Cargoboard, ukryte za typowanymi obiektami żądań i odpowiedzi.

quote()
Wiążąca cena za przewóz wraz z czasem realizacji, oknem doręczenia, pełnym rozbiciem dopłat i wyliczeniem emisji CO2.
bookQuotation()
Rezerwuje dokładnie tę wycenioną cenę, zamiast przeliczać ją ponownie w momencie rezerwacji.
placeOrder()
Rezerwuje bezpośrednio, z pominięciem kroku wyceny, gdy cena nie jest tu istotna.
listQuotations()
fetchQuotation()
Zapisane wyceny wraz z filtrowaniem, stronicowaniem i informacją, czy zostały już zarezerwowane.
listOrders()
fetchOrder()
Zapisane zlecenia: status, fizyczny status przesyłki, pozycje z kodami kreskowymi, partnerzy, faktury i faktycznie rozliczona cena.
cancelOrder()
Anuluje rezerwację - a ponieważ Cargoboard nie ma endpointu aktualizacji, jest to również sposób na jej poprawienie.
fetchLabels()
fetchConfirmation()
Etykiety w formacie A4 albo A6 oraz potwierdzenie zlecenia, jako bajty PDF.
fetchTracking()
Pełny strumień statusów z kamieniami milowymi, lokalizacjami oraz uszczegółowionymi oknami odbioru i doręczenia.
listInvoices()
fetchInvoicePdf()
Faktury z kwotami, terminami płatności i stanem opłacenia lub przeterminowania, wraz z PDF-em każdej z nich.
fetchAdrData()
Dane ADR dla towarów niebezpiecznych po numerze UN, konwertowalne wprost na deklarację przy pozycji ładunku.

Co robi

Najpierw cena, potem rezerwacja tej ceny

Wycena zwraca wiążącą cenę z rozbiciem na pozycje kosztowe - fracht, dopłaty, opłaty drogowe - więc można klientowi pokazać, za co płaci, a następnie zarezerwować to samo ID wyceny bez ponownego przeliczania.

Każdy rodzaj transportu

Drobnica i LTL na palety lub kartony, ładunki częściowe rozliczane metrami ładunkowymi oraz pięć klas całych pojazdów, od busa firanki po ciężarówkę 40-tonową - każda z limitem ładowności i dozwolonymi produktami pilnowanymi lokalnie.

Tryb paczkowy

To samo żądanie, wysłane z jednym nagłówkiem, jest wyceniane i rezerwowane jako paczka, a nie fracht. Przełączenie trybu włącza też zestaw reguł paczkowych: 32 kg wagi rzeczywistej i objętościowej, limity boków i obwodu, dwadzieścia paczek na odbiór dziennie, zakaz towarów niebezpiecznych.

Śledzenie, które da się czytać

Około jedna trzecia zdarzeń w żywym strumieniu nie ma żadnej treści - niesie za to uszczegółowione okna odbioru i doręczenia. describe() renderuje każde zdarzenie jako linijkę przeznaczoną dla człowieka, a strumień jest deduplikowany po identyfikatorze zdarzenia.

Towary niebezpieczne

Dane ADR wyszukiwane po numerze UN i konwertowane wprost na deklarację przy pozycji ładunku, wraz z instrukcjami pakowania, przepisami szczególnymi i sprawdzeniem przepisu szczególnego 188.

Webhooki i linki do śledzenia

Payload Track & Trace jest parsowany do tej samej postaci co zdarzenia z API, a adresy stron śledzenia dla klienta końcowego budowane są automatycznie - łącznie z wariantem pomijającym captchę.

Dwa zestawy reguł, jedno żądanie

Wycena potrzebuje kodu pocztowego i kraju; rezerwacja - nazw, ulic, miast i daty odbioru. Ten sam obiekt żądania jest walidowany według właściwego zestawu, więc brakujące pole rezerwacji kończy się komunikatem przy konkretnym polu, a nie odpowiedzią HTTP 422.

Wyrozumiały tam, gdzie trzeba

Parsowanie jest celowo pobłażliwe: nierozpoznana wartość enuma staje się nullem i zachowuje surowy ciąg, więc nowy kod statusu wymyślony po stronie Cargoboard nie położy działającej integracji.

Walidacja i ostrzeżenia, które nie są błędami

Każda wycena i rezerwacja jest najpierw sprawdzana lokalnie, a komunikaty poprzedzone są tymi samymi ścieżkami pól, których Cargoboard używa we własnych odpowiedziach 422. Reguły obejmują obowiązkowe pola adresowe zależne od trybu, odbiory i doręczenia od poniedziałku do piątku, spójność okna odbioru, daty doręczenia dostępne wyłącznie dla produktów FIX, treść i wymiary pozycji, geometrię metrów ładunkowych, limity ładowności pojazdów wraz z wymaganymi produktami, ubezpieczenie wymagające zadeklarowanej wartości towaru, wartości wyłącznie w euro, deklaracje towarów niebezpiecznych oraz pełny zestaw reguł paczkowych.

Część reguł Cargoboard egzekwuje w oddziale, a nie kodem HTTP. Odbiorca prywatny zarezerwowany bez umówionej dostawy ani zgody na pozostawienie towaru zostanie przez API przyjęty - a kłopot pojawi się w dniu doręczenia. Odrzucenie takiej rezerwacji lokalnie oznaczałoby, że biblioteka poprawia API, więc te przypadki wracają jako ostrzeżenia: logowane przy każdej wycenie i rezerwacji, nigdy rzucane jako wyjątek.

$errors   = $client->validateLocally($request);  // blokuje wywolanie
$warnings = $client->warningsFor($request);      // logowane, nigdy rzucane

Trzy rzeczy, których nie ma w schematach

  • Zdarzenie śledzenia bez treści nie jest zdarzeniem pustym. Mniej więcej jedna trzecia strumienia ma message: null i niesie zamiast tego uszczegółowione okna odbioru i doręczenia - najbardziej użyteczną informację w całym strumieniu. Oczywisty łańcuch wartości domyślnych wypisze dla nich wszystkich goły numer statusu.
  • Identyfikator zdarzenia to jedyny bezpieczny klucz deduplikacji. Przesyłka awizowana telefonicznie i mailem generuje kilka zdarzeń o tym samym kodzie i znaczniku czasu, więc kluczowanie zapisanych wierszy po tej parze po cichu gubi zdarzenia. Identyfikatora nie ma w żadnym opublikowanym schemacie; biblioteka i tak go odczytuje.
  • Wyszukiwarka towarów niebezpiecznych odpowiada kodem 202 z pustym ciałem, gdy nie ma jeszcze w pamięci danego numeru UN. Potraktowana jako sukces sparsowałaby się w deklarację bez klasy zagrożenia, więc zamiast tego rzucany jest dedykowany wyjątek.

Cargoboard nie ma też endpointu aktualizacji. Zarezerwowanego zlecenia nie da się zmienić - można je tylko anulować i zarezerwować ponownie albo poprawić przez wsparcie. Warto zaprojektować się wokół tego przed pierwszym zleceniem, a nie po nim.

Szybki start

composer require very-code-com/cargoboard-php
$client = CargoboardClient::sandbox('your-api-key');

$request = new ShipmentRequest(
    product:   Product::Standard,
    shipper:   new Shipper(
        address:  new Address('40239', CountryCode::DE, 'Duesseldorf', 'Examplestreet 12a'),
        name:     'Producer ABC GmbH & Co. KG',
        pickupOn: '2026-09-01',
    ),
    consignee: new Consignee(
        address: new Address('41061', CountryCode::DE, 'Moenchengladbach', 'Examplestreet 5'),
        name:    'Consignee ABC AG',
    ),
    lines:     [new Line('Werkzeugmaschine', 1, PackageType::EuroPallet, 120, 80, 120, 200.0)],
);

$quotation = $client->quote($request);          // 90.85 EUR, 1-2 dni
$order     = $client->bookQuotation($quotation->id, $request);

file_put_contents('labels.pdf', $client->fetchLabels($order->id));

foreach ($client->fetchTracking($order->id)->timeline() as $event) {
    printf("%-5s %s\n", $event->code, $event->describe());
    // 540   Estimates updated: collection 18.08. 07:00-15:00, delivery 19.08. 06:00 - 21.08. 14:00
}

Na sandboxie nic nie jest realizowane i żadna ciężarówka nie rusza w trasę, choć potwierdzenie i tak przychodzi mailem, więc dane da się sprawdzić. Na produkcji każda rezerwacja to prawdziwy, płatny transport.

Zbudowane tak, żeby działało dalej

Hierarchia wyjątków jest celowo drobnoziarnista - uwierzytelnianie, oczekiwanie na synchronizację danych ADR, brak zasobu, konflikt, dane nie do przetworzenia, limit zapytań z podpowiedzią czasu ponowienia oraz błędy serwera, które same wiedzą, czy warto je powtórzyć - żeby wywołujący mógł złapać dokładnie ten przypadek, na który ma odpowiedź.

Continuous integration uruchamia testy jednostkowe na PHP 8.2, 8.3 i 8.4 plus przebieg na najniższych wersjach zależności, potwierdzający, że deklarowana dolna granica faktycznie działa, a do tego PHPStan poziom 8, audyt zależności wobec bazy podatności i bramkę pokrycia kodu odrzucającą wynik poniżej 85%. Nocny workflow integracyjny rezerwuje, śledzi i anuluje prawdziwe zlecenie sandboxowe od początku do końca.

Jedna wskazówka diagnostyczna warta powtórzenia za README: jeśli klucz, który "powinien działać", zwraca 403, najpierw przepisz go znak po znaku. Cargoboard odpowiada identycznym 403 na klucz z literówką, brakujący nagłówek, klucz z niewłaściwego środowiska, nieaktywowane konto i endpoint, do którego klucz nie ma uprawnień. Ani status, ani ciało odpowiedzi, ani nagłówki tego nie rozróżniają.

Dla kogo?

Dla zespołów PHP, które wożą palety, a nie paczki - producenta wysyłającego maszyny, dystrybutora prowadzącego frachty LTL po Europie, systemu ERP potrzebującego wiążącej ceny wewnątrz koszyka albo procesu zamówień. Sprawdzi się wszędzie tam, gdzie wycena i rezerwacja frachtu mają być zwykłym, otypowanym wywołaniem usługi, z już obsłużonymi i opisanymi nieudokumentowanymi zachowaniami przewoźnika.