DPD

OficjalnyDarmowy

Nadawanie paczek kurierem DPD prosto z zamówienia: etykiety PDF lub ZPL, zamówienie kuriera, śledzenie statusów i link do śledzenia dla klienta.

@core·Wiktor KoźmińskiBrak recenzji5 firm z instalacjąAktualizacja 23 października 2026
Zainstaluj w Orbito Otwiera panel Orbito na karcie modułu
Licencja
Bezpłatny
Wersja
2.8.8
Kategoria
Wysyłka

Moduł DPD (core/dpd)

Przewoźnik dla hubu przesyłek core/shipping 2.0.0. Integruje dwa API DPD:

  • DPD Services (REST) — nadanie przesyłki i pobranie etykiety,
  • DPD InfoServices (SOAP) — zdarzenia przesyłki, na których opierają się statusy.

Cały ruch HTTP idzie przez Core.fetch (proxy rdzenia), więc domeny DPD muszą być zatwierdzone przez administratora firmy.

Moduł jest multi-instancyjny (module.json: "singleton": false) — firma może mieć kilka kont DPD, a każda instalacja to osobna instancja z własną konfiguracją. Tożsamością przewoźnika w hubie jest instanceId instancji modułu, nie kod modułu ani carrierId; hub woła metody przez Core.callInstanceMethod(instanceId, method, params).

Moduł wymaga SDK @orbito/api-lib w wersji ≥ 4.4.1 (RPC po TLS, gdy rdzeń poda ORBITO_TLS_CA; Files — bucket plików instancji, z którego hub bierze logo) oraz dependsOn: ["core/shipping", "core/orders"] — dane zamówienia czyta przez core/orders.getById, a przesyłkę do rejestru zapisuje sam hub.

Hook

core.shipping.getCarrier

Moduł odpowiada na hook hubu jednym obiektem definicji przewoźnika (index.js):

{
    name: config.accountName,           // z konfiguracji `connection.accountName`, domyślnie "DPD"
    moduleCode: Core.getModuleInfo().code,
    instanceId: Core.getInstanceId(),   // TOŻSAMOŚĆ przewoźnika w hubie
    logoFile: 'logo.jpg',               // nazwa pliku w buckecie TEJ instancji
    quickShipOptionsMethod: 'getQuickShipOptions',
    shipmentFormMethod: 'getShipmentForm',
    getTrackingStatusMethod: 'getTrackingStatus',
    generateLabelsMethod: 'generateLabels'
}

Gdy dane dostępowe nie są uzupełnione (brak loginu, hasła lub Master FID) albo odczyt konfiguracji się nie powiedzie, hook zwraca null — instancja nie pojawia się na liście przewoźników hubu.

SDK nie zna po stronie modułu nazwy instancji, dlatego przyjazną nazwę przewoźnika podaje się ręcznie w konfiguracji (connection.accountName). Przy kilku kontach DPD warto ją rozróżnić, np. „DPD — konto główne”, „DPD — sklep B”.

quickShipParamsMethod nie jest zwracane — moduł nie implementuje schematu opcji dla warunków.

Logo przewoźnika

Znak marki leży w repozytorium jako assets/logo.jpg (kwadrat 160 × 160). Przy starcie moduł wrzuca go do bucketu swojej instancji (Files.upload, src/logo.js) i podaje hubowi samą nazwę pliku w logoFile — bajty nigdy nie jadą przez hub. Front pobiera plik osobnym żądaniem po instanceId i nazwie, widokiem OVE type: image.

SDK liczy sha256 lokalnie i najpierw próbuje podpiąć bajty, które rdzeń już zna, więc wrzut przy każdym starcie przenosi plik jeden raz, a nie przy każdym restarcie kontenera.

Nieudany wrzut kończy się ostrzeżeniem w logu i niczego nie przerywa — przewoźnik rejestruje się normalnie, a hub pokazuje wtedy inicjały zamiast logo.

Pole logoFile to rozszerzenie kontraktu getCarrier z huba 2.2.0 — starszy hub pomija je bez błędu i rysuje inicjały.

Metody

MetodaUprawnienieOpis
getQuickShipOptions (orderId)tylko hub (ctx.caller)Jedna opcja szybkiego nadania: „DPD Punkt odbioru”, gdy zamówienie ma delivery.pickupPoint.id, w przeciwnym razie „DPD Kurier”. Opcja niesie opis domyślnej paczki, isCod z zamówienia, instanceId tej instancji, method: 'quickShip' i params: { carrierId }. Brak konfiguracji → [] (hub pokazuje opcje pozostałych przewoźników)
quickShip (orderId, carrierId)tylko hub (ctx.caller)Nadanie przesyłki w DPD na domyślnych ustawieniach modułu (kafelek szybkiego nadania); zwraca CreatedShipment hubu
getShipmentForm (orderId, carrierId?)shipment.formSchemat OVE formularza nadania (kontrakt hubu core/shipping, shipmentFormMethod); brak carrierId → wariant wynika z zamówienia (punkt odbioru → PUDO, inaczej Kurier), tak jak w getQuickShipOptions; błąd konfiguracji lub zamówienia → { error }
createShipment (orderId, carrierId, pudoId?, parcel?, cod?, declaredValue?, ref1?, ref2?, ref3?)tylko hub (ctx.caller)Nadanie przesyłki w DPD wartościami z formularza nadania (kontrakt hubu, wołana przez router ship); zwraca CreatedShipment hubu
getTrackingStatus (trackNo, carrierData?)tylko hub (ctx.caller)Status przesyłki z InfoServices; DPD pyta wyłącznie po numerze listu, carrierData jest ignorowane
generateLabels (shipments[{ _id, trackNo, carrierData }])label.downloadEtykiety PDF przesyłek; zwraca { labels: LabelFile[] }

Wywołania moduł→moduł mają ustawiony przez rdzeń ctx.caller i przechodzą bez tabeli uprawnień użytkownika. Metody kontraktu (getQuickShipOptions, quickShip, createShipment, getTrackingStatus) woła wyłącznie hub — wywołanie bez ctx.caller kończy się błędem INTERNAL_CALL_REQUIRED. generateLabels i getShipmentForm przechodzą zarówno z hubu (ctx.caller), jak i od użytkownika z uprawnieniem odpowiednio label.download i shipment.form.

Metody ładuje Utils.loadMethods z katalogu methods/ — nazwa pliku jest nazwą metody.

quickShip

Warianty nadania (carrierId) to lokalny słownik modułu, a nie tożsamość przewoźnika:

carrierIdNazwaWarunek
dpd_courierDPD Kurierdomyślny
dpd_pickupDPD Punkt odbioruzamówienie ma delivery.pickupPoint.id (usługa DPD_PICKUP / PUDO)

Wynik zgodny z CreatedShipment hubu:

{
    carrier: 'DPD Kurier',      // etykieta wariantu nadania
    trackNo: 'string',          // numer listu przewozowego
    trackingUrl: 'string',
    status: 'sent',
    parcels: [{ x, y, z, weight }],
    codAmount: 0,               // tylko dla zamówień pobraniowych (usługa COD)
    carrierData: {
        carrierId,              // wariant nadania
        sessionId,              // sesja nadania w DPD (klucz do etykiety)
        reference,              // referencja paczki (klucz zapasowy do etykiety)
        waybills,               // wszystkie numery listów z sesji
        environment             // 'sandbox' | 'production'
    }
}

Idempotencji po stronie DPD nie ma — lock per zamówienie trzyma hub (router ship, TTL 180 s), wspólny dla nadania kafelkiem (quickShip) i formularzem (createShipment).

Błędy DPD (walidacja danych, transport, brak konfiguracji, nieznany carrierId) sygnalizujemy wyjątkiem z treścią czytelną dla użytkownika; szczegóły techniczne (kod, status HTTP, externalId) trafiają do Log.error. Częściowy sukces — DPD utworzyło sesję, ale nie zwróciło numeru listu — też jest wyjątkiem, a identyfikator sesji DPD jest doklejany do komunikatu, żeby użytkownik sprawdził przesyłkę w panelu DPD, zanim nada ją ponownie.

getTrackingStatus

Odpytuje InfoServices o najnowsze zdarzenie przesyłki i zwraca TrackingStatus hubu: status (zmapowany z businessCode), statusDate (eventTime), carrierStatus (surowy businessCode). Brak zdarzeń w DPD nie jest błędem — świeżo nadana przesyłka nie ma jeszcze historii, więc metoda zwraca unknown z bieżącą datą (wyjątek cron hubu logowałby jako awarię).

Kanał zapytań to connection.eventsChannel, a gdy jest pusty — connection.masterFid.

generateLabels

Etykiety pobierane są przesyłka po przesyłce (klucz: carrierData.sessionId, a gdy go brak — carrierData.reference), bo wspólne wywołanie DPD zwraca jeden scalony dokument, a hub wymaga shipmentId przy każdym pliku. Wynik to { labels: [{ shipmentId, contentBase64, mimeType, filename }] }.

Format pliku bierze się z defaults.labelFormat — PDF (application/pdf) albo ZPL (application/x-zpl), nieznana wartość spada na PDF — i wyznacza rozszerzenie nazwy (dpd-<trackNo>.pdf / .zpl); rozmiar wydruku pochodzi z defaults.printSize. Błąd pojedynczej etykiety (albo przesyłka bez sessionId i reference) jest logowany Log.warn i pomijany; batch nie jest przerywany, a hub zaloguje braki.

getShipmentForm i createShipment — formularz nadania

getShipmentForm buduje schemat OVE formularza (src/form-view.js, czysta funkcja bez SDK — więc testowalna bez rdzenia) na danych startowych z buildDpdFormData (nadawca z konfiguracji, odbiorca z zamówienia, domyślna paczka, referencje ref1–ref3). Przycisk „Nadaj” w schemacie woła router hubu (core/shipping → ship) z instanceId tej instancji i metodą createShipment; wynik zgłasza nazwanymi akcjami karty przesyłek huba (shipmentCreated / shipmentFailed), bo formularz nie zna identyfikatorów jej widoków.

Zakres pól wersji 1 (jedna paczka na nadanie — wiele paczek to osobny etap):

  • wariant nadania (Kurier / Punkt odbioru) i kod punktu odbioru (PUDO), pole widoczne tylko dla

wariantu Punkt odbioru (renderIf),

  • wymiary paczki (sizeX/sizeY/sizeZ w cm) i waga w kg,
  • pobranie (COD) i ubezpieczenie — wartość deklarowana (usługa DECLARED_VALUE, nowa w

src/packageData.js; kwota 0 w obu polach oznacza rezygnację z usługi),

  • referencje ref1–ref3 (prefill z buildDpdFormData),
  • nadawca i odbiorca — tylko podgląd, bez edycji w tej wersji.

createShipment (tylko hub, requireModuleCaller) nadaje przez src/ship.js (issueShipment) — funkcję wydzieloną z dawnego methods/quickShip.js, więc walidacja wariantu, wywołanie DPD i budowa CreatedShipment są jednym kodem dla kafelka i formularza; różni je tylko zestaw wartości w form (formularz przekazuje wymiary, PUDO, kwoty i referencje z przeglądarki, kafelek nadaje na samych domyślnych ustawieniach modułu).

Konfiguracja

Bloki rejestruje src/register-config.js (Config.registerConfigBlocks), wszystkie w zasięgu global. Konfigurację czyta loadConfig (src/module-config.js); moduł uznaje się za skonfigurowany, gdy ma login, password i masterFid.

connection — Połączenie z DPD

PoleOpis
accountNameNazwa przewoźnika widoczna w module przesyłek (name w getCarrier), domyślnie „DPD”
environmentsandbox (dpdservicesdemo) lub production (dpdservices.dpd.com.pl); domyślnie sandbox
loginLogin DPD Services (wymagany)
passwordHasło DPD Services (wymagane, SECRET)
masterFidNumer FID płatnika / klienta DPD (wymagany)
eventsChannelOpcjonalny identyfikator kanału powiadomień — nadpisuje FID przy odpytywaniu statusów (SOAP)

Walidacja bloku sprawdza obecność loginu, hasła i Master FID, a następnie realnie testuje połączenie wywołaniem checkPostCode w DPD Services. Nieudane połączenie zgłaszane jest jako błąd pola login (i logowane Log.warn).

defaults — Domyślne nadanie

Wartości używane przy szybkim nadaniu oraz jako startowe w formularzu nadania: labelFormat (PDF / ZPL — format pliku etykiety w generateLabels), printSize (A4 / LBL_PRINTER), weight [kg], sizeX, sizeY, sizeZ [cm], content (zawartość paczki) oraz customerData1–customerData3 — źródło referencji ref1–ref3 (brak / ID zamówienia / numer zamówienia / e-mail kupującego).

Brak wypełnionych wartości domyślnych jest bezpieczny: waga schodzi do 1 kg, wymiary do 20 cm, a referencje do pustych wartości.

sender — Dane nadawcy

Dane przekazywane do DPD przy każdym nadaniu: company, name, address, city, postalCode, countryCode (np. PL), phone, email. Należy wypełnić wszystkie pola wymagane przez umowę z DPD.

Uprawnienia

Rejestrowane w src/register-permissions.js, egzekwowane przez requirePermission(code, ctx):

KodOpis
shipment.formFormularz nadania DPD
label.downloadPobieranie etykiet DPD

Mapowanie statusów

src/StatusesMap.js tłumaczy kod businessCode ze zdarzenia DPD InfoServices na status hubu (core.shipping/src/lib/shipment-status.js). Kody pogrupowane są w: waiting, sent, in-road, ready_to_pickup, delivered, delivery-problems, returned. Kod spoza katalogu daje unknown — przesyłka zostaje wtedy w statusach „otwartych”, więc cron hubu odpyta ją ponownie.

Status liczony jest z najnowszego zdarzenia, które nie jest powiadomieniem mail/SMS (StatusesMap.isNotificationCode, np. 170310 „Powiadomienie EMAIL”). Dlatego InfoServicesApi.getLatestEvent pyta DPD z eventsSelectType=ALL, a nie ONLY_LAST: DPD rejestruje powiadomienie kilka minut po doręczeniu, więc ONLY_LAST zwracało je zamiast doręczenia i przesyłka wisiała jako in-road. Gdy przesyłka ma wyłącznie powiadomienia, brane jest najnowsze z nich (in-road).

Źródło listy kodów: oficjalny katalog „DPD Infoservices Events” (DPD Infoservices Events_05-02-2026.xlsx).

Testy

source ~/.nvm/nvm.sh && nvm use 22 && npm test

Testy node:test wymagają Node ≥ 22. SDK @orbito/api-lib jest podmieniany mockiem (test/helpers/api-mock.js, ładowanym przez --require), więc testy nie dotykają rdzenia ani sieci.

Różnice względem 1.x

  • moduł odpowiada na jeden hook hubu — core.shipping.getCarrier. Stary model hooków

(core.shipping.issueLabel, core.shipping.trackShipment, core.shipping.getShipmentFormData) zniknął, bo hub 2.0.0 ich nie emituje,

  • tożsamością przewoźnika jest instanceId instancji modułu, a nie carrierId; carrierId

(dpd_courier / dpd_pickup) to już tylko lokalny wariant nadania przekazywany w params opcji,

  • moduł jest multi-instancyjny (singleton: false) — jedna firma może mieć kilka kont DPD; nazwę

przewoźnika podaje nowe pole connection.accountName,

  • rezerwacje nadania (validateIssueReservation) zostały usunięte — lock per zamówienie trzyma hub,

więc idempotencja po stronie DPD nie jest już potrzebna,

  • błędy nadania są wyjątkami zamiast wyniku { carrierData: { error } } — walidatory hubu i tak

odrzucają odpowiedź bez trackNo,

  • przesyłkę zapisuje hub (router ship), moduł niczego nie rejestruje po nadaniu,
  • statusy odpytuje cron hubu — moduł nie ma własnych zadań cyklicznych,
  • getShipmentFormData zniknęła — hub 2.3.0 dodał kontraktowi pole shipmentFormMethod, więc

jej dane startowe (buildDpdFormData) zasilają teraz pełny schemat OVE formularza zwracany przez getShipmentForm, a nadanie z formularza obsługuje nowa metoda createShipment.

Poza zakresem tej iteracji

  • quickShipParamsMethod — schemat opcji przewoźnika dla warunków doboru w hubie.
  • Formularz nadania (getShipmentForm) pokazuje dane nadawcy i odbiorcy tylko do wglądu;

edycja stron przesyłki pozostaje poza zakresem. Usługi dodatkowe DPD spoza pobrania i wartości deklarowanej (InfoServicesApi, lista dynamiczna) nie są jeszcze wystawione.

Podpaczki w ręcznym nadaniu (2.4.0)

DPD Kurier: do 50 paczek (limit modułu). DPD Pickup w Polsce: jedna paczka; dla odbiorcy zagranicznego lista pozostaje dostępna. Źródło: DPD REST Services — DPD_PICKUP.

Formularz otwierany z dropdownu pokazuje „Paczka #1”, „Paczka #2” itd. „Dodaj podpaczkę” kopiuje bieżące wymiary i wagę ostatniej paczki; każdą kopię można edytować niezależnie. Usuwanie jest dostępne, gdy pozostałaby przynajmniej jedna paczka. Zmiana wariantu dostawy zachowuje listę, ale nadanie wielu paczek w usłudze jednopaczkowej jest blokowane po stronie serwera. Pobranie i ubezpieczenie dotyczą całej przesyłki.

createShipment przyjmuje parcels[]. Dotychczasowy parcel i szybkie nadanie zachowują obsługę jednej paczki. Wymiary i waga każdej podpaczki muszą być dodatnie. Hub zapisuje pełne parcels[]; zwrócone przez przewoźnika numery podpaczek trafiają do carrierData.waybills. Śledzenie statusu nadal korzysta z głównego numeru przesyłki. Etykiety są pobierane dla całej przesyłki/sesji.

Daty — Europe/Warsaw

Czas zdarzenia eventTime bez offsetu jest interpretowany jako lokalny czas Europe/Warsaw. Timestamp z offsetem lub Z zachowuje swój moment. Do hubu trafia jednoznaczna data UTC; ta sama normalizacja obowiązuje przy wyborze najnowszego zdarzenia.

Zmiana uwzględnia czas letni i zimowy. Nie wykonuje migracji istniejących danych.

Zamówienie kuriera na wybraną datę (2.5.0)

W formularzu nadania wybierz „Przekazanie przesyłki → Zamów kuriera na wybrany dzień”, a następnie datę i przedział godzin odbioru. Kalendarz podpowiada dzisiejszą datę w Europe/Warsaw; godziny startowe to 10:00–16:00 i można je zmienić.

Moduł tworzy list przewozowy, a potem jedno zlecenie podjazdu dla wszystkich podpaczek tego nadania przez POST /public/courierorder/v1/packagesPickupCall. Dane nadawcy pochodzą z ustawień modułu. Odbiór wymaga polskiego adresu nadawcy, telefonu, numerycznego Master FID oraz paczek do 50 kg i 300 cm na wymiar. DPD potwierdza dostępność wskazanego terminu. Przesyłki zagraniczne korzystają z typu zlecenia INTERNATIONAL_EXPRESS (przewóz drogowy), krajowe z DOMESTIC.

Parametry formularza: dropOffType (REGULAR_PICKUP lub REQUEST_COURIER), pickupDate (RRRR-MM-DD), pickupTimeFrom i pickupTimeTo (GG:MM). Puste, nieistniejące i przeszłe daty oraz nieprawidłowe przedziały godzin są odrzucane przed utworzeniem listu. Szybkie nadanie i domyślne ręczne nadanie pozostają w trybie bez zamawiania kuriera.

Dane odbioru są zapisywane w carrierData.pickup: date, timeFrom, timeTo, status, a po potwierdzeniu również orderNumber i checkSum. Jeżeli podjazd zostanie odrzucony lub jego wynik jest niepewny, istniejąca przesyłka nadal trafia do rejestru huba z numerem listu. Formularz pokazuje trwałe powiadomienie, aby sprawdzić lub zamówić odbiór w panelu DPD bez ponownego nadawania przesyłki. Żądanie zamówienia kuriera nigdy nie jest automatycznie ponawiane.

Kontrakt sprawdzony z oficjalną dokumentacją DPD REST Services — Courier order.

Publiczny link śledzenia

Hook shipping.carrier.getTrackingUrl przyjmuje { courierName, trackNo }. Jest odczytem bez efektów: zwraca { trackingUrl } z istniejącej lokalnej funkcji budującej URL albo null, gdy nazwa nie należy do tego przewoźnika lub numer jest pusty. Nazwy porównuje po normalizacji wielkości liter i odstępów. Nie wymaga połączenia z API przewoźnika, nie sprawdza statusu przesyłki i nie wybiera konta nadawcy. Odkrywanie instancji odbywa się przez shipping.carrier.getTrackingUrl.providers, a żądanie przez standardowy kontrakt z ID instancji i uwierzytelnionym kontekstem.

Plany opakowań

Hub przesyłek może przekazać potwierdzony packingPlan przez kontrakt shipping.carrier. Wymiary i waga brutto pochodzą z planu, a adapter ponownie sprawdza zgodność z aktualnym zamówieniem przed nadaniem. Zmiana zamówienia wymaga ponownego potwierdzenia. Obsługa jest deklarowana przez packingPlans: true oraz packingPlanVersions: [1, 2]. Wersja 2 obsługuje zestawy z osobnymi trybami połączenia kartonów; do API trafia wynikowa lista paczek. Nadal obowiązują limity usług i liczby paczek; starszy hub zachowuje dotychczasowy sposób nadania.

Wspiera wiele paczek w jednej przesyłce, z ograniczeniami konkretnej usługi.

Gdy usługa nie obsługuje podpaczek w jednym nadaniu, aktualny Shipping może nadać każdą fizyczną paczkę na osobnym liście (separateShipments, packingParcelIndex). Pełny plan jest ponownie walidowany przed każdym nadaniem. Pobranie jest dzielone według sum maksymalnych obciążeń kartonów w paczce, w groszach, tak aby suma na wszystkich listach była dokładnie równa kwocie zamówienia. Każdy zapisany list ma własną etykietę i zdarzenie do istniejącej automatyzacji wydruku. Częściowy sukces lub niepewna odpowiedź zatrzymuje serię i blokuje automatyczne ponowienie.