# Packeta (Zásilkovna) integration Packeta delivers mainly to **pickup points** and **Z-BOX lockers** (plus home delivery in some regions). It's the most common choice for SK/CZ eshops. This repo is already **scaffolded** for Packeta's pickup-point picker — you mostly need an API key to switch it on. Shipment creation via API is extra, optional work. --- ## 1. Get a Packeta account & keys 1. Register a client account at (Zásilkovna / Packeta). For SK: . 2. In the client portal open **Client support → API / Nastavenia API** (or "Integrations"). You get **two different secrets** — don't mix them up: - **Web/Widget API key** — public-ish key used by the browser pickup-point widget (`Packeta.Widget.pick`). This is the one this repo already uses. - **API password (REST/SOAP)** — secret server key used to *create packets* (shipments). Never expose this to the browser. 3. (For real shipping) configure your **sender/pickup address and label format** in the portal. --- ## 2. Activate the pickup-point picker (already built) The checkout template already loads the widget and wires the chosen point into the order **whenever `packeta_api_key` is non-empty** (`assets/views/shop/checkout.html`): - loads `https://widget.packeta.com/v6/www/js/library.js` - `Packeta.Widget.pick(packetaKey, point => …)` fills hidden `pickup_point_id` + `pickup_point_name` - if the key is empty it falls back to a plain text field So to turn it on: ### a) Set the Web/Widget API key Set the env var (read by `config/development.yaml` / `production.yaml` → `settings.packeta_api_key`, exposed via `src/shared/settings.rs`): ```bash # .env (development) or your production environment PACKETA_API_KEY=your_web_widget_api_key ``` `config/development.yaml` already contains: ```yaml settings: packeta_api_key: {{ get_env(name="PACKETA_API_KEY", default="") }} ``` For production, add the same line under `settings:` in `config/production.yaml` (it isn't there yet). ### b) Create a Packeta delivery option in the admin Go to **`/admin/shipping`** → "Add delivery option": - **Name**: e.g. `Packeta – pickup point` - **Price**: your fee (e.g. `2.90`) - ✅ **Requires pickup point** ← this makes the picker appear at checkout - ✅ **Active** The auto-generated `code` will be `packeta-pickup-point` (or similar). Customers now see the option, click "Choose pickup point", pick on the map, and the order stores `pickup_point_id` + `pickup_point_name`. **At this point you have a working Packeta flow** — you read the pickup point on the order in `/admin/orders` and create the parcel manually in the Packeta portal. Many small shops stop here. --- ## 3. (Optional) Create shipments via API Automate "register the parcel + get tracking + print label". Do the [shared groundwork](README.md#shared-groundwork-do-this-once-before-any-carriers-api-step) first (HTTP client, `integrations` module, `carrier` column, tracking columns). ### Endpoint & auth - Packeta REST API base: `https://www.zasilkovna.cz/api/rest` (SOAP also available at `http://www.zasilkovna.cz/api/soap.wsdl`). - Auth = your **API password** (the server secret from step 1), sent in the request body, **not** the widget key. - Key operation: **`createPacket`**. You send sender id, recipient name/email/phone, the chosen **pickup point id** (`addressId`), value, weight, and COD amount; you receive a **packet id + barcode (tracking)**. A separate **`packetLabelPdf`** call returns the label PDF. ### Store the secret ```bash PACKETA_API_PASSWORD=your_secret_api_password ``` Add to `config/*.yaml` under `settings:`: ```yaml packeta_api_password: {{ get_env(name="PACKETA_API_PASSWORD", default="") }} ``` ### Client sketch (`src/integrations/packeta.rs`) ```rust use loco_rs::prelude::*; use crate::shared::settings; // createPacket accepts XML; serde_json works for the JSON REST variant. pub async fn create_shipment(ctx: &AppContext, req: super::ShipmentRequest<'_>) -> Result { let api_password = settings::get(ctx, "packeta_api_password") .ok_or_else(|| Error::string("packeta_api_password not configured"))?; // Packeta's createPacket is XML/SOAP-ish; build the body per their docs. // number = your order_number // name/surname = recipient // addressId = req.pickup_point_id (the chosen point) // cod = req.cod_cents / 100 (0 if not COD) // value = goods value // eshop = your sender label/id from the portal let body = format!(r#" {api_password} {} {} {} {} {} {} {} YOUR_SENDER_LABEL "#, req.order_number, req.recipient_name, req.email, req.pickup_point_id.unwrap_or(""), req.cod_cents as f64 / 100.0, req.cod_cents as f64 / 100.0, req.weight_grams); let resp = reqwest::Client::new() .post("https://www.zasilkovna.cz/api/rest") .body(body) .send().await.map_err(|e| Error::string(&e.to_string()))? .text().await.map_err(|e| Error::string(&e.to_string()))?; // Parse (packet id) and (tracking) out of the XML response. // Then optionally call packetLabelPdf with that id to fetch the label. todo!("parse resp into ShipmentResult") } ``` Then call it from your admin "Create shipment" action for orders whose `shipping_methods.carrier == "packeta"`, and save `tracking_number` / `shipment_id` back on the order. --- ## 4. Testing - Use the Packeta **sandbox/staging** portal if your account offers one, or a test API password. Verify `createPacket` returns a packet id before going live. - Track the parcel at `https://tracking.packeta.com/...` using the returned barcode. ## 5. Go-live checklist - [ ] `PACKETA_API_KEY` (widget) set in production env - [ ] `packeta_api_key` line added under `settings:` in `config/production.yaml` - [ ] Packeta delivery option created in `/admin/shipping` with **Requires pickup point** ✅ - [ ] (If using API) `PACKETA_API_PASSWORD` set + `src/integrations/packeta.rs` implemented - [ ] Sender address & label format configured in the Packeta portal - [ ] Test order → pickup point saved on order → (API) tracking number stored