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

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:

    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:

    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:

    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:

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> { ... }
  • packeta.md — Packeta / Zásilkovna (pickup points + home, SK/CZ-centric)
  • dpd.md — DPD (home delivery + Pickup parcelshops)
  • 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.