shipping
This commit is contained in:
147
docs/integrations/dpd.md
Normal file
147
docs/integrations/dpd.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user