shipping
This commit is contained in:
150
docs/integrations/dhl.md
Normal file
150
docs/integrations/dhl.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# DHL integration
|
||||
|
||||
DHL is best for **home delivery and international/express** shipments. Like DPD,
|
||||
**nothing DHL-specific is scaffolded** here. DHL is mostly an **address (home)
|
||||
delivery** carrier — pickup points exist (DHL ServicePoint / Packstation, mostly
|
||||
DE) but most shops use DHL for door-to-door, so you can usually skip the pickup
|
||||
widget entirely.
|
||||
|
||||
> DHL has **several separate APIs** behind one developer portal
|
||||
> (<https://developer.dhl.com>). Pick the one that matches your service:
|
||||
> - **DHL Parcel DE (Post & Parcel Germany) — Shipping API** for German domestic
|
||||
> parcels / Packstation.
|
||||
> - **DHL eCommerce (Parcel) APIs** for various countries.
|
||||
> - **DHL Express — MyDHL API** for international express.
|
||||
> Confirm which your contract covers before coding.
|
||||
|
||||
---
|
||||
|
||||
## 1. Get DHL API access
|
||||
|
||||
1. Create an account on the **DHL Developer Portal**: <https://developer.dhl.com>.
|
||||
2. Create an **app** and subscribe it to the specific API you need (e.g.
|
||||
"Shipping API" or "MyDHL API"). You receive an **API key (client id) +
|
||||
secret**.
|
||||
3. Separately you need a **DHL business/customer account** (EKP / account
|
||||
number, billing number) — the developer key alone can't bill shipments. Link
|
||||
your business account credentials to the app.
|
||||
4. Most DHL APIs use **OAuth2 client-credentials**: you exchange key+secret for a
|
||||
short-lived **Bearer token**, then call the shipping endpoints with it. (Some
|
||||
older endpoints use Basic auth — check your API's docs.)
|
||||
|
||||
---
|
||||
|
||||
## 2. Create the delivery option
|
||||
|
||||
At **`/admin/shipping`** → "Add delivery option":
|
||||
- **Name**: e.g. `DHL` or `DHL Express (international)`
|
||||
- **Price**: your fee
|
||||
- **Requires pickup point**: ❌ off for normal home delivery
|
||||
(turn ✅ on *only* if you specifically offer DHL Packstation/ServicePoint and
|
||||
build a picker — see section 4)
|
||||
- ✅ **Active**
|
||||
|
||||
With the option active, customers can already choose DHL and you can create the
|
||||
label manually in DHL Business Customer Portal. The API (section 3) automates
|
||||
that.
|
||||
|
||||
---
|
||||
|
||||
## 3. Create shipments via the DHL API
|
||||
|
||||
Do the [shared groundwork](README.md#shared-groundwork-do-this-once-before-any-carriers-api-step)
|
||||
first. Set `shipping_methods.carrier = "dhl"` for your DHL options.
|
||||
|
||||
### Credentials
|
||||
|
||||
```bash
|
||||
DHL_API_KEY=your_client_id
|
||||
DHL_API_SECRET=your_client_secret
|
||||
DHL_ACCOUNT_NUMBER=your_ekp_or_billing_number
|
||||
DHL_API_BASE=https://api-eu.dhl.com # depends on the specific API
|
||||
```
|
||||
Add matching lines under `settings:` in `config/*.yaml`:
|
||||
```yaml
|
||||
dhl_api_key: {{ get_env(name="DHL_API_KEY", default="") }}
|
||||
dhl_api_secret: {{ get_env(name="DHL_API_SECRET", default="") }}
|
||||
dhl_account_number: {{ get_env(name="DHL_ACCOUNT_NUMBER", default="") }}
|
||||
dhl_api_base: {{ get_env(name="DHL_API_BASE", default="") }}
|
||||
```
|
||||
|
||||
### Flow (OAuth2 + create shipment)
|
||||
|
||||
1. **Token** → `POST {base}/.../token` with `grant_type=client_credentials` +
|
||||
key/secret → `access_token` (Bearer; cache until it expires).
|
||||
2. **Create shipment** → `POST` the shipment-orders endpoint with the Bearer
|
||||
token: shipper (your account/EKP), consignee (recipient from the order),
|
||||
product code (domestic vs international/express), weight, customs data for
|
||||
non-EU, and references (`order_number`). COD is a value-added service if you
|
||||
offer it.
|
||||
3. **Label** → the response includes a **tracking/shipment number** and a
|
||||
**label** (PDF/base64). Store/print it.
|
||||
|
||||
### Client sketch (`src/integrations/dhl.rs`)
|
||||
|
||||
```rust
|
||||
use loco_rs::prelude::*;
|
||||
use crate::shared::settings;
|
||||
|
||||
async fn bearer(ctx: &AppContext) -> Result<String> {
|
||||
let base = settings::get(ctx, "dhl_api_base").unwrap_or_default();
|
||||
let key = settings::get(ctx, "dhl_api_key").unwrap_or_default();
|
||||
let secret = settings::get(ctx, "dhl_api_secret").unwrap_or_default();
|
||||
// POST client_credentials → access_token; cache with expiry.
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn create_shipment(ctx: &AppContext, req: super::ShipmentRequest<'_>)
|
||||
-> Result<super::ShipmentResult>
|
||||
{
|
||||
let token = bearer(ctx).await?;
|
||||
let account = settings::get(ctx, "dhl_account_number").unwrap_or_default();
|
||||
// Build shipment JSON:
|
||||
// - shipper: your account address (account = EKP/billing number)
|
||||
// - consignee: req.recipient_name / address / city / zip / country
|
||||
// - details: weight, product code (domestic / express), currency
|
||||
// - refs: req.order_number
|
||||
// - for international: customs (HS codes, declared value, contents)
|
||||
// POST {base}/.../shipments with Authorization: Bearer {token}
|
||||
todo!("parse tracking number + label into ShipmentResult")
|
||||
}
|
||||
```
|
||||
|
||||
Wire into the admin "Create shipment" action for `carrier == "dhl"` orders.
|
||||
|
||||
> 🌍 **International note:** for shipments outside the EU customs union you must
|
||||
> send **customs/commodity data** (HS codes, declared value, item descriptions).
|
||||
> Your `order_items` only store name + price today — if you ship internationally
|
||||
> you'll likely add a customs description/HS-code field to products.
|
||||
|
||||
---
|
||||
|
||||
## 4. (Optional) DHL pickup points
|
||||
|
||||
If you offer **Packstation / ServicePoint**, set "Requires pickup point" ✅ on
|
||||
that delivery option and render DHL's **Location Finder** (a separate DHL API)
|
||||
in the checkout pickup block (the `x-show="requiresPoint"` section of
|
||||
`assets/views/shop/checkout.html`), writing the chosen locker id into the
|
||||
existing hidden `pickup_point_id` / `pickup_point_name` fields. For Packstation
|
||||
you also need the recipient's **DHL post number** — an extra field most shops
|
||||
avoid unless targeting Germany.
|
||||
|
||||
---
|
||||
|
||||
## 5. Testing
|
||||
|
||||
- DHL provides a **sandbox** environment per API (separate base URL + test
|
||||
credentials) on the developer portal. Get a token and create one test
|
||||
shipment there before production.
|
||||
- Validate the tracking number on <https://www.dhl.com/track>.
|
||||
|
||||
## 6. Go-live checklist
|
||||
|
||||
- [ ] DHL developer app created + subscribed to the right API
|
||||
- [ ] DHL business account (EKP/billing number) linked
|
||||
- [ ] `DHL_*` env vars set; matching `settings:` lines added to `config/production.yaml`
|
||||
- [ ] Delivery option created in `/admin/shipping`; `carrier = "dhl"` set
|
||||
- [ ] `src/integrations/dhl.rs` implemented; OAuth token caching working
|
||||
- [ ] (International) customs data available on products/items
|
||||
- [ ] Test shipment in DHL sandbox → tracking number stored on order
|
||||
- [ ] Switched from sandbox to production base URL/credentials
|
||||
Reference in New Issue
Block a user