shipping
This commit is contained in:
120
docs/integrations/README.md
Normal file
120
docs/integrations/README.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Carrier integrations
|
||||
|
||||
This eshop manages **delivery options** as plain rows in the `shipping_methods`
|
||||
table (admin UI at `/admin/shipping` — add / edit price + toggle / remove). A
|
||||
delivery option is just a name, a price, and two flags. **None of that talks to
|
||||
a carrier yet** — it only decides what the customer can pick and how much they
|
||||
pay.
|
||||
|
||||
Integrating a real carrier (Packeta, DPD, DHL) means wiring two *separate*
|
||||
concerns on top of an existing delivery option:
|
||||
|
||||
1. **Pickup-point selection** (checkout, browser-side) — only for carriers that
|
||||
deliver to pickup points / lockers. The customer picks a point via the
|
||||
carrier's JS map widget; the chosen id + name land in the order.
|
||||
2. **Shipment creation** (server-side, after the order is placed) — you call the
|
||||
carrier's HTTP API to register the parcel, then store the returned tracking
|
||||
number and print the label.
|
||||
|
||||
These are independent: you can ship to a Packeta pickup point manually (no API)
|
||||
just by enabling the pickup widget, and you can create DHL labels via API for a
|
||||
home-delivery option that has no pickup point at all.
|
||||
|
||||
> ❗ This is **not** a many-to-many / database relationship between your tables.
|
||||
> A carrier is an **external HTTP API** you call from the server. The only
|
||||
> schema you add is a few columns (which carrier a method maps to; a tracking
|
||||
> number on the order) — see "Shared groundwork" below.
|
||||
|
||||
## What already exists in the codebase
|
||||
|
||||
| Piece | Where | Status |
|
||||
|---|---|---|
|
||||
| Delivery option CRUD | `src/controllers/admin_shipping.rs`, `assets/views/admin/shipping/index.html` | ✅ done |
|
||||
| `shipping_methods` table (`code`, `name`, `price_cents`, `requires_pickup_point`, `enabled`, `position`) | `migration/.../m20260616_150755_shipping_methods.rs` | ✅ done |
|
||||
| Carrier choice + pickup fields on checkout | `assets/views/shop/checkout.html` (`carrier_code`, `pickup_point_id`, `pickup_point_name`) | ✅ done |
|
||||
| Order stores carrier + pickup point | `orders` table (`carrier_code`, `carrier_name`, `pickup_point_id`, `pickup_point_name`, `shipping_cents`) | ✅ done |
|
||||
| Settings lookup | `src/shared/settings.rs` → reads `settings.*` from `config/*.yaml` | ✅ done |
|
||||
| Packeta pickup-point widget | `assets/views/shop/checkout.html` (loads when `packeta_api_key` set) | ✅ scaffolded |
|
||||
| Shipment-creation API client (any carrier) | — | ❌ not built |
|
||||
| Tracking number on order | — | ❌ not built |
|
||||
|
||||
So **pickup-point selection for Packeta is already wired** — it just needs an
|
||||
API key. Everything else (DPD/DHL widgets, and *all* shipment-creation API
|
||||
calls) is new work, described per carrier.
|
||||
|
||||
## Shared groundwork (do this once, before any carrier's API step)
|
||||
|
||||
The pickup-widget half needs nothing new. The **shipment-creation** half needs:
|
||||
|
||||
1. **An HTTP client dependency.** Add to `Cargo.toml`:
|
||||
```toml
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
```
|
||||
(Loco already pulls `tokio`/`serde`/`serde_json`.)
|
||||
|
||||
2. **A place for carrier clients.** Create `src/integrations/mod.rs` and a file
|
||||
per carrier (`packeta.rs`, `dpd.rs`, `dhl.rs`). Register `pub mod integrations;`
|
||||
in `src/lib.rs` (next to `pub mod controllers;` etc.).
|
||||
|
||||
3. **Map a delivery option to a carrier.** Add a `carrier` column to
|
||||
`shipping_methods` so each admin-created option knows which API (if any) to
|
||||
call. Generate the migration:
|
||||
```bash
|
||||
cargo loco generate migration add_carrier_to_shipping_methods carrier:string
|
||||
```
|
||||
Values: `none` (manual, the default), `packeta`, `dpd`, `dhl`. Then add a
|
||||
`<select name="carrier">` to the add-form in
|
||||
`assets/views/admin/shipping/index.html` and persist it in
|
||||
`admin_shipping::create`.
|
||||
|
||||
4. **Store the tracking number / label on the order.** Generate:
|
||||
```bash
|
||||
cargo loco generate migration add_tracking_to_orders \
|
||||
tracking_number:string shipment_id:string label_url:string
|
||||
```
|
||||
|
||||
5. **A "Create shipment" admin action.** In the admin order detail
|
||||
(`src/controllers/admin_orders.rs`), add a button/handler that: looks up the
|
||||
order's `carrier_code` → finds the `shipping_methods.carrier` → calls the
|
||||
matching `integrations::<carrier>::create_shipment(...)` → saves
|
||||
`tracking_number` + `label_url` back onto the order. Optionally do this
|
||||
automatically in `orders::place`, but a manual admin trigger is safer to
|
||||
start (you can review the order first).
|
||||
|
||||
After the groundwork, each carrier file implements one async function roughly
|
||||
like:
|
||||
|
||||
```rust
|
||||
pub struct ShipmentRequest<'a> {
|
||||
pub order_number: &'a str,
|
||||
pub recipient_name: &'a str,
|
||||
pub email: &'a str,
|
||||
pub phone: Option<&'a str>,
|
||||
pub address: Option<&'a str>,
|
||||
pub city: Option<&'a str>,
|
||||
pub zip: Option<&'a str>,
|
||||
pub country: Option<&'a str>,
|
||||
pub pickup_point_id: Option<&'a str>,
|
||||
pub cod_cents: i64, // 0 unless cash-on-delivery
|
||||
pub currency: &'a str,
|
||||
pub weight_grams: i32,
|
||||
}
|
||||
|
||||
pub struct ShipmentResult {
|
||||
pub shipment_id: String,
|
||||
pub tracking_number: String,
|
||||
pub label_url: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn create_shipment(ctx: &AppContext, req: ShipmentRequest<'_>) -> loco_rs::Result<ShipmentResult> { ... }
|
||||
```
|
||||
|
||||
## Read next
|
||||
|
||||
- [`packeta.md`](packeta.md) — Packeta / Zásilkovna (pickup points + home, SK/CZ-centric)
|
||||
- [`dpd.md`](dpd.md) — DPD (home delivery + Pickup parcelshops)
|
||||
- [`dhl.md`](dhl.md) — DHL (international, Parcel/Express)
|
||||
|
||||
> ⚠️ Carrier APIs change. Treat the endpoint names, field names, and auth
|
||||
> details here as a **map of the moving parts**, and confirm exact request
|
||||
> formats against each carrier's current developer portal before coding.
|
||||
Reference in New Issue
Block a user