Developer / API
DPD API client
Yui\Dpd\services\DpdClient wraps the DPD REST API (SK/CZ region) over Basic Auth, switching
between production (https://api.dpd.sk/v1) and sandbox (https://api.test.dpd.sk/v1) — or a
custom URL override from settings — based on the Sandbox mode switch:
| Method | Endpoint | Purpose |
|---|---|---|
createParcel(array $payload) | POST /parcels | Register a new shipment. |
downloadLabel(string $parcelId, string $format = 'pdf') | GET /parcels/{id}/label | Fetch the raw label PDF. |
getTracking(string $parcelId) | GET /parcels/{id}/tracking | Fetch tracking events for a parcel. |
getParcel(string $parcelId) | GET /parcels/{id} | Fetch parcel details. |
getPickupPoints(array $query) | GET /parcelshops | List ParcelShop pickup points. |
testConnection() | GET /parcelshops (limit 1) | Used by Test Connection in Settings. |
Every request/response error is normalized into an InvalidConfigException with a human-readable
message (DPD's errors/error/message/detail fields are extracted when present) and logged
through StorePlugin::error() under dpd:client:*.
Shipment creation and order automation
Yui\Dpd\services\ShipmentService::syncOrder() builds the DPD parcel payload
(buildParcelPayload() — recipient from the order's shipping address, sender from the configured
pickup address, weight from the order or the Default parcel weight fallback, and a
cash-on-delivery amount when the order isn't paid yet), calls DpdClient::createParcel(), and
persists the result as a Yui\Dpd\records\ShipmentRecord (table {{%dpd_shipments}}). It then
fetches and caches the label via LabelService::fetchAndCacheLabel() and, if the storefront plugin
is present, adds an order comment noting the parcel number.
Automatic sync is wired in Plugin::init(): on OrdersService::EVENT_AFTER_ORDER_STATUS_CHANGE,
if the order's new status matches Auto-create shipment on status and
ShipmentService::shouldSyncOrder() (order uses the dpd shipping method) and
hasReachedLabelLimit() allow it, syncOrder() runs and the result is logged under
dpd:order-sync.
Labels are stored on disk under storage/dpd/labels/ (path recorded on the ShipmentRecord as
label_file_path), so getLabelForShipment() serves from cache before hitting the API again.
TrackingService::refreshStatusForShipment() re-pulls the DPD tracking response and updates the
stored status; getTimelineForShipment() normalizes DPD's tracking events (or falls back to the
current status alone) into a display-ready timeline.
Checkout pickup-point widget
When Enable ParcelShop pickup points is on and DPD passes checkRestrictions(), Plugin::init()
renders src/templates/shipping-code.twig into the storefront's shared remote-checkout-scripts
view hook. The template defines an Alpine component, initDpdPickupPoints(), which:
- Fetches pickup points from the anonymous site-facing endpoint on init and on ZIP-code search,
using the plugin handle (
dpd) as thecountryquery parameter — the template setsDPD_COUNTRY = handle is defined ? handle : 'SK', andPlugin::init()always renders it withhandle => self::PLUGIN_HANDLE. The Default country for pickup points setting (Settings::$pickupPointCountry) is read only byPickupPointService::getPickupPointsForCheckout(), which nothing in the plugin currently calls, so it has no effect on this request. - Renders the list, letting the customer pick one.
- On selection, dispatches two
windowCustomEvents:dpd-shipping-selected(detail: the raw point) andshipping-option-data(detail:{ method: 'dpd', pickupPoint: point }), which the checkout's shipping-option handling consumes.
Plugin::shippingDetails() also exposes an action block (trigger: 'action-dpd',
init: 'initDpdPickupPoints()') so the shipping-methods module knows to render the same component
inline in the method's own action area, not only via the checkout-scripts hook.
Site-facing endpoint
dpd/api/pickup-points (anonymous, GET) is the only endpoint DPD exposes to the storefront:
GET /index.php?p=dpd/api/pickup-points&country=SK&zipCode=83101
{ "success": true, "points": [
{ "id": "12345", "name": "ParcelShop Example", "street": "...", "city": "...",
"zipCode": "83101", "country": "SK", "latitude": 48.15, "longitude": 17.11,
"openingHours": "...", "type": "PARCELSHOP", "raw": { "...": "..." } }
] }
Results are cached for one hour per country/ZIP combination
(PickupPointService::CACHE_DURATION) via Craft's application cache. On an API failure, the
endpoint returns { "success": false, "points": [] } rather than surfacing the exception to
anonymous callers.
The endpoint itself accepts any country value, but the checkout widget documented above always
calls it with country=dpd (the plugin handle), not a real ISO country code — this looks like an
unintended bug rather than by design, since a pickupPointCountry setting exists specifically to
configure this and is simply never wired up to the checkout call.
Shipping method registration
Like every yStore carrier plugin, Plugin registers itself with the shared
ShippingMethodRegistry on Plugins::EVENT_AFTER_LOAD_PLUGINS. Plugin::shippingDetails() is the
method metadata consumed by the shipping-methods module (price, restrictions, the pickup-point
action config described above); Plugin::checkRestrictions() is the single source of truth for
whether the method is currently offered (enabled flag + visibility window).
CP routes worth knowing about
| Route | Purpose |
|---|---|
dpd/dashboard | Shipment statistics dashboard |
dpd/dashboard/save-layout | Persists dashboard widget layout |
dpd/settings | Plugin/shipping method settings form |
dpd/api/test-connection | Tests the configured API credentials |
dpd/shipments | Shipment list (status/search filters, CSV export) |
dpd/shipments/{id} | Shipment detail: parcel info, delivery address, tracking timeline, raw payload/response |
dpd/shipments/create/{orderId} | Create a shipment for an order |
dpd/shipments/refresh/{id} | Refresh tracking status from DPD |
dpd/shipments/resync/{id} | Create a new shipment/label for the same order |
dpd/shipments/{id}/label | View or download (?download=1) the stored label PDF |
dpd/shipments/delete-label/{id} | Delete the stored label file (keeps the DPD parcel) |
dpd/license | License management panel (activate/redeem/revoke/copy token) |
dpd/api/pickup-points | Anonymous site-facing endpoint used by the checkout widget |
All routes above except dpd/api/pickup-points require a CP request; dpd/api/pickup-points is
the only one reachable from the storefront.