# DPD integration DPD offers **home/business delivery** and **DPD Pickup parcelshops & lockers**. Unlike Packeta, **nothing DPD-specific is scaffolded yet** in this repo, so this is a full integration: an optional pickup widget plus the shipment-creation API. --- ## 1. Get a DPD account & API access 1. You need a **business contract** with DPD in your country (e.g. DPD SK: ). Ask your account manager for **API access**. 2. DPD exposes a few different APIs depending on country/era — confirm which one your contract uses: - **REST Shipping API** (modern; JSON) — most new integrations. - **SOAP "Login/Shipment" web services** (older; still common in CEE). - Some markets use the **DPD Geodata / Shop Finder API** for parcelshops. 3. You'll receive: an **API base URL**, a **delisId / login**, and a **password** (the SOAP `login` call returns a short-lived **auth token** you reuse on subsequent calls). REST variants use an API key/token directly. 4. Note your **sender address** and **DPD customer number** — required on every shipment. --- ## 2. Decide which DPD services you offer Create one delivery option per service at **`/admin/shipping`** → "Add delivery option": | Option | "Requires pickup point" | Notes | |---|---|---| | `DPD Home` (classic) | ❌ off | delivered to the address on the order | | `DPD Pickup` (parcelshop/locker) | ✅ on | customer must choose a shop/locker | For `DPD Pickup` you need a **point picker** (section 3). For `DPD Home` you can skip straight to the API (section 4). --- ## 3. (Pickup only) Add the DPD parcelshop picker at checkout The checkout already has the generic pickup machinery: when the selected method has `requires_pickup_point = true`, the block with hidden `pickup_point_id` / `pickup_point_name` shows (`assets/views/shop/checkout.html`, the `x-show= "requiresPoint"` block). Today that block only renders the **Packeta** widget (guarded by `{% if packeta_api_key %}`) or a text fallback. To support DPD you make that block carrier-aware: 1. Pass a `dpd_enabled` / map-widget key flag into the checkout context from `src/controllers/checkout.rs` (like `packeta_api_key` is passed today). 2. In the pickup block, branch on the chosen `carrier` (the Alpine `carrier` variable already holds the method `code`) and render DPD's parcelshop map widget when a DPD pickup method is selected. DPD provides an embeddable **map/widget** (or you query their **Shop Finder API** and render your own list); on selection, write the shop id into `pointId` and a human label into `pointName` — exactly what the existing hidden inputs expect. No new order fields are needed — `pickup_point_id` / `pickup_point_name` already carry the DPD shop id + name. --- ## 4. Create shipments via the DPD API Do the [shared groundwork](README.md#shared-groundwork-do-this-once-before-any-carriers-api-step) first. Set `shipping_methods.carrier = "dpd"` for your DPD options. ### Auth (SOAP-style, common in CEE) ```bash DPD_API_BASE=https://api.dpd.sk # from your account manager DPD_LOGIN=your_delis_login DPD_PASSWORD=your_password DPD_CUSTOMER_NUMBER=your_customer_no ``` Add matching lines under `settings:` in `config/*.yaml`: ```yaml dpd_api_base: {{ get_env(name="DPD_API_BASE", default="") }} dpd_login: {{ get_env(name="DPD_LOGIN", default="") }} dpd_password: {{ get_env(name="DPD_PASSWORD", default="") }} dpd_customer_number: {{ get_env(name="DPD_CUSTOMER_NUMBER", default="") }} ``` ### Flow 1. **Login** → `LoginService.getAuth(delisId, password)` returns an **auth token** (valid for a while; cache it). 2. **Create shipment** → `ShipmentService.storeOrders(...)` with the auth token, recipient address (or parcelshop id for Pickup), parcel weight, references (your `order_number`), and COD amount if cash-on-delivery. Returns a **parcel number (MPS id)** = your tracking number, plus label data. 3. **Label** → the same call (or `getParcelLabels`) returns a **PDF/ZPL label**; store or print it. ### Client sketch (`src/integrations/dpd.rs`) ```rust use loco_rs::prelude::*; use crate::shared::settings; async fn auth_token(ctx: &AppContext) -> Result { let base = settings::get(ctx, "dpd_api_base").unwrap_or_default(); let login = settings::get(ctx, "dpd_login").unwrap_or_default(); let pass = settings::get(ctx, "dpd_password").unwrap_or_default(); // POST login → parse token from response. Cache it (e.g. in-memory w/ expiry). todo!() } pub async fn create_shipment(ctx: &AppContext, req: super::ShipmentRequest<'_>) -> Result { let token = auth_token(ctx).await?; let customer = settings::get(ctx, "dpd_customer_number").unwrap_or_default(); // Build storeOrders payload: // - product: "CL" (classic/home) or "Pickup" + parcelShopId = req.pickup_point_id // - recipient: req.recipient_name / address / city / zip / country / phone // - cod: req.cod_cents (set cash-on-delivery service if > 0) // - reference: req.order_number // POST to {base}/shipment ... with `token`. todo!("parse parcel number + label into ShipmentResult") } ``` Wire it into the admin "Create shipment" action for `carrier == "dpd"` orders. --- ## 5. Testing - DPD provides a **test/integration environment** (separate base URL + credentials) — get it from your account manager. Validate login + one shipment there first. - Confirm the returned parcel number tracks on `https://tracking.dpd.de/...` / your local DPD tracking site. ## 6. Go-live checklist - [ ] DPD business contract + API credentials obtained - [ ] `DPD_*` env vars set; matching `settings:` lines added to `config/production.yaml` - [ ] Delivery option(s) created in `/admin/shipping` (`DPD Home` and/or `DPD Pickup`) - [ ] `carrier = "dpd"` set on those methods (via the shared `carrier` column) - [ ] (Pickup) parcelshop picker rendered in checkout for DPD methods - [ ] `src/integrations/dpd.rs` implemented; login token caching working - [ ] Test shipment in DPD test env → tracking number stored on order - [ ] Switched base URL/credentials from test to production