# 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
