6.1 KiB
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
- You need a business contract with DPD in your country (e.g. DPD SK: https://www.dpd.com/sk). Ask your account manager for API access.
- 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.
- You'll receive: an API base URL, a delisId / login, and a
password (the SOAP
logincall returns a short-lived auth token you reuse on subsequent calls). REST variants use an API key/token directly. - 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:
- Pass a
dpd_enabled/ map-widget key flag into the checkout context fromsrc/controllers/checkout.rs(likepacketa_api_keyis passed today). - In the pickup block, branch on the chosen
carrier(the Alpinecarriervariable already holds the methodcode) 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 intopointIdand a human label intopointName— 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
first. Set shipping_methods.carrier = "dpd" for your DPD options.
Auth (SOAP-style, common in CEE)
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:
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
- Login →
LoginService.getAuth(delisId, password)returns an auth token (valid for a while; cache it). - Create shipment →
ShipmentService.storeOrders(...)with the auth token, recipient address (or parcelshop id for Pickup), parcel weight, references (yourorder_number), and COD amount if cash-on-delivery. Returns a parcel number (MPS id) = your tracking number, plus label data. - Label → the same call (or
getParcelLabels) returns a PDF/ZPL label; store or print it.
Client sketch (src/integrations/dpd.rs)
use loco_rs::prelude::*;
use crate::shared::settings;
async fn auth_token(ctx: &AppContext) -> Result<String> {
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<super::ShipmentResult>
{
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; matchingsettings:lines added toconfig/production.yaml- Delivery option(s) created in
/admin/shipping(DPD Homeand/orDPD Pickup) carrier = "dpd"set on those methods (via the sharedcarriercolumn)- (Pickup) parcelshop picker rendered in checkout for DPD methods
src/integrations/dpd.rsimplemented; login token caching working- Test shipment in DPD test env → tracking number stored on order
- Switched base URL/credentials from test to production