shipping
This commit is contained in:
174
docs/integrations/packeta.md
Normal file
174
docs/integrations/packeta.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 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 <https://client.packeta.com> (Zásilkovna /
|
||||
Packeta). For SK: <https://www.packeta.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<super::ShipmentResult>
|
||||
{
|
||||
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#"<createPacket>
|
||||
<apiPassword>{api_password}</apiPassword>
|
||||
<packetAttributes>
|
||||
<number>{}</number>
|
||||
<name>{}</name>
|
||||
<email>{}</email>
|
||||
<addressId>{}</addressId>
|
||||
<cod>{}</cod>
|
||||
<value>{}</value>
|
||||
<weight>{}</weight>
|
||||
<eshop>YOUR_SENDER_LABEL</eshop>
|
||||
</packetAttributes>
|
||||
</createPacket>"#,
|
||||
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 <id> (packet id) and <barcode> (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
|
||||
Reference in New Issue
Block a user