DPD
OficjalnyDarmowyNadawanie paczek kurierem DPD prosto z zamówienia: etykiety PDF lub ZPL, zamówienie kuriera, śledzenie statusów i link do śledzenia dla klienta.
- 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
| Metoda | Uprawnienie | Opis |
|---|---|---|
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.form | Schemat 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.download | Etykiety 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:
carrierId | Nazwa | Warunek |
|---|---|---|
dpd_courier | DPD Kurier | domyślny |
dpd_pickup | DPD Punkt odbioru | zamó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/sizeZw 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 zbuildDpdFormData), - 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
| Pole | Opis |
|---|---|
accountName | Nazwa przewoźnika widoczna w module przesyłek (name w getCarrier), domyślnie „DPD” |
environment | sandbox (dpdservicesdemo) lub production (dpdservices.dpd.com.pl); domyślnie sandbox |
login | Login DPD Services (wymagany) |
password | Hasło DPD Services (wymagane, SECRET) |
masterFid | Numer FID płatnika / klienta DPD (wymagany) |
eventsChannel | Opcjonalny 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):
| Kod | Opis |
|---|---|
shipment.form | Formularz nadania DPD |
label.download | Pobieranie 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
instanceIdinstancji modułu, a niecarrierId;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,
getShipmentFormDatazniknęła — hub 2.3.0 dodał kontraktowi poleshipmentFormMethod, 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.
