Files
kompress_eshop/docs/integrations/dpd.md
2026-06-17 16:15:22 +02:00

148 lines
6.1 KiB
Markdown

# 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:
<https://www.dpd.com/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<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; 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