# Vyzdvihnutie a doručenie (vlastná flotila)

Táto príručka opisuje API miestneho doručenia firemného účtu: objednávky, ktoré vlastní vodiči firmy doručia príjemcovi (`type` `D`) alebo vyzdvihnú u odosielateľa (`type` `P`). Jedna sada endpointov oceňuje, vytvára, označuje štítkami, sleduje a ruší oba druhy zastávok a webhooky hlásia každú zmenu vášmu systému. Je určená vývojárom systémov na správu objednávok, ERP a internetových obchodov, ktoré odovzdávajú prácu vlastnej flotile firmy.

## 1. Čo môžete vytvoriť

Nasledujúce príklady sledujú jednu firmu: **Farine & Fils**, dodávateľa pre pekárne so skladom na adrese 2200 Rue Cohen, Saint-Laurent, QC (H4R 2N6), ktorý doručuje veľkoobchodné objednávky po celom ostrove Montreal a vyzdvihuje prázdne prepravky na chlieb, ktoré jeho zákazníci vracajú. Typickým doručením je jeden stoh prepraviek s hmotnosťou 12 kg a rozmermi 60 × 40 × 30 cm pre Café Lumière, 5400 Avenue du Parc, Montréal (H2V 4G7), pod veľkoobchodnou objednávkou `WHS-20931`. Typickým vyzdvihnutím je jeden stoh prázdnych prepraviek s hmotnosťou 4 kg z Épicerie Wellington, 4100 Rue Wellington, Verdun (H4G 1V5), pod referenciou `CRT-20931`.

- **Veľkoobchodné objednávky odoslané na dispečing z ERP.** Každá potvrdená veľkoobchodná objednávka sa stane objednávkou doručenia s ranným časovým oknom doručenia kaviarne a ERP uloží vrátené sledovacie číslo k riadku objednávky.
- **Vyzdvihnutie vrátených prepraviek.** Keď zákazník nahlási prázdne prepravky, ERP vytvorí objednávku vyzdvihnutia na adresu zákazníka a vodič prepravky vyzdvihne na nasledujúcej trase.
- **Tlač štítkov v sklade.** ERP stiahne PDF štítku každej objednávky a vytlačí ho pri nakladacej rampe, takže každý stoh prepraviek nesie svoj sledovací čiarový kód.
- **Zákaznícky portál s aktuálnym stavom.** Každá kaviareň vidí stav svojich doručení a vyzdvihnutí vrátane dokladu o doručení, pričom údaje prichádzajú cez webhooky namiesto opakovaného dopytovania.

## 2. Čo táto príručka pokrýva

Túto príručku použite, keď objednávku prepravujú vlastní vodiči firmy: doručenia zo skladu a vyzdvihnutia z adresy zákazníka, vytvárané jednotlivo alebo v dávkach cez endpointy `/api/v1/client/...` a `/api/v1/orders/...`.

Pre nové integrácie je odporúčaným jediným vstupným bodom Uniorder (`/api/v1/uniorder/...`): ponúka rovnaké doručenia vlastnou flotilou cez jedno API spolu so štítkami dopravcu z jednej cenovej ponuky. Prehľad nájdete v príručke **Uniorder: jedno API pre každú zásielku** a požiadavky krok za krokom v príručke **Cenová ponuka a objednávka v jednom toku**. Endpointy v tejto príručke zostávajú dostupné a nezmenené pre integrácie, ktoré sú na nich postavené.

Príručku **Štítky dopravcu** použite, keď balík prepravuje externý dopravca so štítkom kúpeným cez platformu. Príručku **Prepravné služby** použite pre objednávky, ktoré zákaznícky účet rezervuje u služieb firmy, a príručku **Skladovanie a výdaj** pre tovar uložený v sklade a odosielaný na požiadanie; na tieto dva prípady sa Uniorder nevzťahuje.

## 3. Skôr než začnete

- **Účet.** Použite firemný (klientsky) účet alebo zamestnanecký účet firmy s oprávnením na API. Vytváranie objednávok navyše vyžaduje oprávnenie na zadávanie objednávok; bez neho `POST /api/v1/client/orderCreate` vráti `401`.
- **Oblasť služby.** Adresa doručenia alebo vyzdvihnutia musí byť v aktívnej oblasti firmy. Na testy použite adresy v oblasti, napríklad adresy z tejto príručky.
- **Testovacie údaje.** Použite testovacie referencie, napríklad `WHS-20931` a `CRT-20931`, a testovacie objednávky na konci zrušte (krok 12).
- **Tokeny.** Prístupový token si vyžiadajte zo svojho servera a uchovávajte ho tam. Nikdy ho neposielajte do prehliadača ani do mobilnej aplikácie.
- **Zástupné hodnoty.** Nahraďte `YOUR_HOST` hostiteľom API vášho prostredia a `ACCESS_TOKEN` tokenom z kroku 4.
- **Jednotky.** `weight_unit`: `1` g, `2` kg, `3` oz, `4` lb. `dimension_unit`: `1` mm, `2` cm, `3` m, `4` in. Predvolená hodnota oboch je `1`.

## 4. Prihlásenie

Každé volanie v tejto príručke okrem verejného sledovania sa vykonáva v mene firemného účtu. Prihláste sa raz zo svojho servera, uložte vrátený token a posielajte ho pri každej požiadavke.

**REST:** `POST /api/v1/user/login` — [Príručka REST](/api/documentation#/paths/v1-user-login/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/user/login \
  -H "Content-Type: application/json" \
  -d '{"email":"dispatch@farineetfils.example","password":"your_password"}'
```

```json
{
  "result": true,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
}
```

- `access_token`: vložte ho do hlavičky každej ďalšej požiadavky:

```
Authorization: Bearer ACCESS_TOKEN
```

GraphQL používa rovnakú hlavičku na `POST /api/graphql`.

**GraphQL:** `userLogin` ([Príručka GraphQL](/api/graphql/documentation#/user/userLogin))

**Overenie:** prihlásenie vráti `access_token`. Následujúce požiadavky bez tohto tokenu vrátia `401`.

## 5. Cenová ponuka doručenia alebo vyzdvihnutia (voliteľne)

Cenová ponuka ukáže cenu zastávky ešte pred existenciou objednávky, napríklad na zobrazenie poplatku za doručenie na veľkoobchodnej faktúre. Nič nevytvára a vytvorenie objednávky nevyžaduje predchádzajúcu cenovú ponuku. Nastavte `type` na `D` (doručenie) alebo `P` (vyzdvihnutie); `to_postcode` je PSČ zastávky.

**REST:** `POST /api/v1/orders/rate` — [Príručka REST](/api/documentation#/paths/v1-orders-rate/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/orders/rate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "D",
    "from_postcode": "H4R2N6",
    "from_country": "CA",
    "to_postcode": "H2V4G7",
    "to_country": "CA",
    "packages": [{
      "weight": 12,
      "weight_unit": 2,
      "length": 60,
      "width": 40,
      "height": 30,
      "dimension_unit": 2
    }]
  }'
```

```json
{
  "result": true,
  "shipping_price": "18.50",
  "currency": "CAD",
  "price_details": {
    "shipping_fee": "18.50",
    "tax_details": [
      { "tax_name": "GST", "tax_rate": "5.00", "tax": "0.93" },
      { "tax_name": "QST", "tax_rate": "9.975", "tax": "1.85" }
    ]
  }
}
```

- `shipping_price`: cena zastávky bez dane. Prázdna cena znamená, že PSČ nie je v aktívnej oblasti alebo cenník preň nemá riadok.
- `price_details.tax_details`: dane, ktoré bude objednávka niesť; zobrazte ich na riadku faktúry.
- `currency`: mena všetkých súm v odpovedi.

Na cenovú ponuku vyzdvihnutia prepraviek pošlite rovnakú požiadavku s `"type": "P"`, `"to_postcode": "H4G1V5"` a s hmotnosťou a rozmermi stohu prepraviek.

**GraphQL:** `ordersRate` ([Príručka GraphQL](/api/graphql/documentation#/orders/ordersRate)). Výsledkom je JSON skalár a neprijíma selection set.

```graphql
mutation {
  ordersRate(
    type: "P"
    from_postcode: "H4R2N6"
    from_country: "CA"
    to_postcode: "H4G1V5"
    to_country: "CA"
    packages: [{ weight: 4, weight_unit: 2, length: 60, width: 40, height: 30, dimension_unit: 2 }]
  )
}
```

**Overenie:** `result` je `true` a `shipping_price` je číslo pre `type` `D` aj `type` `P`. Vytvorenie objednávky od tohto kroku nezávisí.

## 6. Vytvorenie objednávky doručenia

Každá potvrdená veľkoobchodná objednávka sa stane jednou objednávkou doručenia. ERP uloží vrátené `id` a `tracking_number` k svojmu riadku objednávky; každé ďalšie volanie používa jedno z nich.

**REST:** `POST /api/v1/client/orderCreate` — [Príručka REST](/api/documentation#/paths/v1-client-orderCreate/post)

Pošlite hlavičku `Idempotency-Key`, jedinečnú pre každú veľkoobchodnú objednávku, aby opakovanie po vypršaní časového limitu nemohlo vytvoriť druhú objednávku.

```bash
curl -X POST https://YOUR_HOST/api/v1/client/orderCreate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: whs-20931-delivery" \
  -d '{
    "type": "D",
    "need_pick_up": 0,
    "ref": "WHS-20931",
    "name": "Marie Tremblay",
    "company_name": "Café Lumière",
    "telephone": "5145550142",
    "email": "commandes@cafelumiere.example",
    "address_1": "5400 Avenue du Parc",
    "city": "Montréal",
    "province": "QC",
    "postcode": "H2V4G7",
    "country": "Canada",
    "schedule_date": "2026-10-02",
    "time_window_start": "2026-10-02 06:00:00",
    "time_window_end": "2026-10-02 09:00:00",
    "packages": 1,
    "packagesDetail": [{
      "ref": "WHS-20931-1",
      "weight": 12,
      "weight_unit": 2,
      "length": 60,
      "width": 40,
      "height": 30,
      "dimension_unit": 2
    }],
    "delivery_instruction": "Deliver to the back door on Rue Saint-Viateur"
  }'
```

```json
{
  "result": true,
  "id": 12345,
  "ref": "WHS-20931",
  "shipping_price": "18.50",
  "currency": "CAD",
  "price_details": { "shipping_fee": "18.50" },
  "tracking_number": ["SR123456789012"],
  "packages": [
    { "id": 67890, "ref": "WHS-20931-1", "tracking_number": "SR123456789012", "external_tracking_number": "" }
  ]
}
```

| Pole | Význam |
|---|---|
| `type` | `D` doručenie alebo `P` vyzdvihnutie |
| `need_pick_up` | `0` — tovar je už v sklade. `1` — vodič musí balík vyzdvihnúť |
| `ref` | Externá referencia na vyhľadávanie a odsúhlasenie |
| `name` / adresa | Doručenie: príjemca. Vyzdvihnutie: zastávka vyzdvihnutia |
| `schedule_date`, `time_window_start`, `time_window_end` | Dátum doručenia (`Y-m-d`) a časové okno, v ktorom sa musí zastávka obslúžiť (`Y-m-d H:i:s`) |
| `packagesDetail` | Jedna položka na balík; `ref` identifikuje balík vo vašom systéme |
| `auto_deduplication` | `1` odmietne druhý balík s rovnakým `ref` balíka |

V odpovedi:

- `id`: id objednávky; uložte ho pre detail objednávky a volanie zrušenia.
- `tracking_number`: jedno sledovacie číslo na balík; podľa nich tlačte a sledujte.
- `warning`: prítomné, keď bola objednávka vytvorená s upozornením, napríklad pri adrese mimo oblasti doručenia, ktorú firma ponecháva alebo pozastavuje. Ponechaná objednávka mimo oblasti môže vrátiť `shipping_price: null`.

**GraphQL:** `clientOrderCreate` ([Príručka GraphQL](/api/graphql/documentation#/client/clientOrderCreate)). Výsledkom je JSON skalár s rovnakým telom ako odpoveď REST.

```graphql
mutation {
  clientOrderCreate(
    type: "D"
    need_pick_up: 0
    ref: "WHS-20931"
    name: "Marie Tremblay"
    company_name: "Café Lumière"
    telephone: "5145550142"
    address_1: "5400 Avenue du Parc"
    city: "Montréal"
    province: "QC"
    postcode: "H2V4G7"
    country: "Canada"
    packages: 1
    packagesDetail: [{ ref: "WHS-20931-1", weight: 12, weight_unit: 2, length: 60, width: 40, height: 30, dimension_unit: 2 }]
  )
}
```

**Overenie:** pošlite rovnaké telo s rovnakým `Idempotency-Key` znova. Odpoveď nesie rovnaké `id` a druhá objednávka sa nevytvorí.

## 7. Vytvorenie objednávky vyzdvihnutia

Objednávka vyzdvihnutia pošle vodiča vyzdvihnúť tovar na adrese; v tomto prípade prázdne prepravky v Épicerie Wellington. Používa rovnaký endpoint ako doručenie: adresa je zastávka vyzdvihnutia, `type` je `P` a `need_pick_up` je `1`.

**REST:** `POST /api/v1/client/orderCreate` — [Príručka REST](/api/documentation#/paths/v1-client-orderCreate/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/client/orderCreate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: crt-20931-pickup" \
  -d '{
    "type": "P",
    "need_pick_up": 1,
    "ref": "CRT-20931",
    "name": "Luc Gagnon",
    "company_name": "Épicerie Wellington",
    "telephone": "5145550187",
    "email": "luc@epiceriewellington.example",
    "address_1": "4100 Rue Wellington",
    "city": "Verdun",
    "province": "QC",
    "postcode": "H4G1V5",
    "country": "Canada",
    "packages": 1,
    "packagesDetail": [{
      "ref": "CRT-20931-1",
      "weight": 4,
      "weight_unit": 2,
      "length": 60,
      "width": 40,
      "height": 30,
      "dimension_unit": 2
    }],
    "pickup_instruction": "Empty crates are stacked at the loading door"
  }'
```

```json
{
  "result": true,
  "id": 12346,
  "ref": "CRT-20931",
  "shipping_price": "12.00",
  "currency": "CAD",
  "tracking_number": ["SR123456789029"],
  "packages": [
    { "id": 67891, "ref": "CRT-20931-1", "tracking_number": "SR123456789029", "external_tracking_number": "" }
  ]
}
```

- `id` a `tracking_number`: uložte ich k vráteniu prepraviek, rovnako ako pri doručení.
- `pickup_instruction`: zobrazí sa vodičovi na zastávke vyzdvihnutia; `delivery_instruction` je jeho obdoba pri doručení.

**Overenie:** detail objednávky (krok 8) ukazuje pre túto objednávku `type` `P` a `need_pickup` `1`.

## 8. Načítanie objednávky

Detail objednávky potvrdí, čo sa uložilo, a vráti aktuálny stav; endpoint zoznamu umožňuje ERP odsúhlasiť vlastné záznamy s platformou.

**REST:** `GET /api/v1/orders/{orderId}` — [Príručka REST](/api/documentation#/paths/v1-orders-orderId/get)

```bash
curl https://YOUR_HOST/api/v1/orders/12345 \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

```json
{
  "business_name": "Farine & Fils",
  "order": {
    "id": 12345,
    "ref": "WHS-20931",
    "type": "D",
    "need_pickup": 0,
    "orders_status_id": 2,
    "name": "Marie Tremblay",
    "address_1": "5400 Avenue du Parc",
    "postcode": "H2V4G7",
    "time_window_start": "2026-10-02 06:00:00",
    "time_window_end": "2026-10-02 09:00:00",
    "shipping_price": "18.50"
  },
  "tracking_numbers": ["SR123456789012"],
  "packages": [
    { "id": 67890, "ref": "WHS-20931-1", "tracking_number": "SR123456789012" }
  ]
}
```

- `order.orders_status_id`: stav objednávky; `2` je Nová, `12` je Zrušená.
- `order.type` a `order.need_pickup`: potvrdzujú, či bola zastávka uložená ako doručenie alebo vyzdvihnutie.
- `tracking_numbers`: sledovacie čísla balíkov objednávky.

**REST:** `GET /api/v1/orders/list` — [Príručka REST](/api/documentation#/paths/v1-orders-list/get)

```bash
curl "https://YOUR_HOST/api/v1/orders/list?page=1&per_page=50" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

Zoznam vráti všetky objednávky účtu od najnovšej, každú s jej balíkmi a položkami. Na stránkovanie pošlite `page` a `per_page` spolu (`per_page` najviac 1000); bez nich sa vráti najnovších 1000 objednávok s príznakom `truncated`.

**GraphQL:** `orders` ([Príručka GraphQL](/api/graphql/documentation#/orders/orders)) pre jednu objednávku a `ordersList` ([Príručka GraphQL](/api/graphql/documentation#/orders/ordersList)) pre zoznam. Obe vracajú JSON skalár.

```graphql
query {
  orders(orderId: "12345")
}
```

**Overenie:** objednávka patrí k prihlásenému účtu, `ref` zodpovedá hodnote odoslanej pri vytvorení a `tracking_numbers` zodpovedá odpovedi na vytvorenie.

## 9. Tlač miestneho štítku

Štítok nesie sledovací čiarový kód, ktorý vodič naskenuje v sklade a na zastávke. Vytlačte jeden štítok na balík a pripevnite ho na stoh prepraviek.

**REST:** `POST /api/v1/shipping/getShippingLabel` — [Príručka REST](/api/documentation#/paths/v1-shipping-getShippingLabel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/shipping/getShippingLabel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "SR123456789012",
    "type": "TRACKING_NUMBER",
    "base64": 1,
    "hide_sender_address": 0,
    "hide_receiver_address": 0
  }'
```

```json
"JVBERi0xLjcKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZwo..."
```

- `type`: ako sa číta `id`: `TRACKING_NUMBER` (predvolené), `ORDER_ID` alebo `REF`.
- `base64`: `0` (predvolené) odošle PDF ako prúd. `1` zmení celé telo odpovede na JSON reťazec najvyššej úrovne obsahujúci PDF v base64, nie na objekt s poľom `pdf_data`. Ak chcete dostať štítok v bežnom JSON objekte, zavolajte namiesto toho `POST /api/v2/shipping/getShippingLabel` — [Príručka REST](/api/documentation#/paths/v2-shipping-getShippingLabel/post).
- `packages`: voliteľné; počet štítkov na tlač. Hodnota odlišná od počtu balíkov objednávky objednávku aktualizuje.
- `hide_sender_address` / `hide_receiver_address`: `1` ponechá danú adresu na štítku prázdnu.

**GraphQL:** `shippingGetShippingLabel` ([Príručka GraphQL](/api/graphql/documentation#/shipping/shippingGetShippingLabel)). `shippingGetShippingLabelV2` ([Príručka GraphQL](/api/graphql/documentation#/shipping/shippingGetShippingLabelV2)) vždy vracia JSON (`pdf_data`).

**Overenie:** dekódované PDF sa otvorí. Štítok doručenia ukazuje adresu Café Lumière; štítok vyzdvihnutia ukazuje adresu Épicerie Wellington. Skrytá adresa je na štítku prázdna.

## 10. Sledovanie objednávky

Verejné sledovanie vráti časovú os udalostí balíka. Nevyžaduje prístupový token, takže ho zákaznícky portál môže zobraziť priamo; spolu s ním prichádza doklad o doručení alebo vyzdvihnutí.

**REST:** `GET /api/v1/tracking/{trackingNumber}` — [Príručka REST](/api/documentation#/paths/v1-tracking-trackingNumber/get)

```bash
curl https://YOUR_HOST/api/v1/tracking/SR123456789012
```

```json
{
  "result": true,
  "postcode": "H2V4G7",
  "deliveried": false,
  "returntosender": false,
  "rejectedbyrecipient": false,
  "data": [
    {
      "tracking_event_status_id": 100,
      "description": "Order information submitted",
      "updated_at_localized": "2026-10-01 16:42:10"
    }
  ],
  "proofs": []
}
```

Rovnaká URL prijíma aj váš `ref`, ak bol uložený ako externé číslo.

Rozhodujte podľa `tracking_event_status_id`, nie podľa `description`; tento reťazec sa riadi hlavičkou `Accept-Language`.

| `tracking_event_status_id` | Strana | Význam |
|---|---|---|
| `100` | obe | Objednávka prijatá |
| `300` / `301` | doručenie | V prevádzke |
| `450` | doručenie | Na ceste k príjemcovi |
| `500` | doručenie | Doručené |
| `501` | doručenie | Doručenie zlyhalo, potrebný nový plán |
| `460` | vyzdvihnutie | Na ceste na vyzdvihnutie |
| `510` | vyzdvihnutie | Vyzdvihnuté |
| `512` | vyzdvihnutie | Vyzdvihnutie zlyhalo, skúsiť neskôr |
| `513` | vyzdvihnutie | Problém s vyzdvihnutím |

- `data`: od najnovšej; prvý riadok je aktuálny stav.
- `deliveried`: `true` po `500`.
- `proofs[]`: pri `500` alebo `510` môže obsahovať `type` `1` (podpis) alebo `2` (fotografia) s `file_id` a `signed_url`. Fotografia nahratá po tejto udalosti v tomto obsahu nie je; prihláste sa na odber `pod.files_updated` (krok 11).

**GraphQL:** `trackingPublic` ([Príručka GraphQL](/api/graphql/documentation#/tracking/trackingPublic)). Výsledok je typovaný a vyžaduje selection set.

```graphql
query {
  trackingPublic(trackingNumber: "SR123456789012") {
    result
    deliveried
    returntosender
    rejectedbyrecipient
    postcode
    data {
      tracking_event_status_id
      tracking_event_key
      description
      updated_at_localized
    }
    proofs { file_id type full_url signed_url }
  }
}
```

**Overenie:** hneď po vytvorení je najnovšia udalosť `100` a `deliveried` je `false`. Neznáme číslo vráti `result: false` s `404`; zobrazte stav nenájdené a nevytvárajte umelé sledovacie udalosti.

## 11. Prijímanie webhookov

Webhooky posielajú každú zmenu na váš server, takže ERP a zákaznícky portál zostávajú aktuálne bez opakovaného dopytovania. Zaregistrujte URL spätných volaní, ktoré tento tok potrebuje:

| Nastavenie | Udalosť | Použitie |
|---|---|---|
| `order_create_webhook_url` | `order.created` | Uloženie `id` a `tracking_number` |
| `order_status_change_webhook_url` | `order.status_change` | Stav pre zákazníka |
| `tracking_event_webhook_url` | `tracking.event` | Časová os vyzdvihnutia alebo doručenia |
| `pod_files_webhook_url` | `pod.files_updated` | Fotografia alebo podpis po vyzdvihnutí alebo doručení |
| `order_cancel_failed_webhook_url` | `order.cancel_failed` | Vami odoslané zrušenie bolo odmietnuté |
| `order_create_async_postback_url` | `order.create_async` | Výsledok asynchrónnej dávky (krok 13) |

**REST:** `PUT /api/v1/webhook-settings` — [Príručka REST](/api/documentation#/paths/v1-webhook-settings/put)

```bash
curl -X PUT https://YOUR_HOST/api/v1/webhook-settings \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "order_create_webhook_url": "https://erp.farineetfils.example/hooks/superroute",
    "order_status_change_webhook_url": "https://erp.farineetfils.example/hooks/superroute",
    "tracking_event_webhook_url": "https://erp.farineetfils.example/hooks/superroute",
    "pod_files_webhook_url": "https://erp.farineetfils.example/hooks/superroute",
    "order_cancel_failed_webhook_url": "https://erp.farineetfils.example/hooks/superroute",
    "webhook_sign_secret": "YOUR_LONG_RANDOM_SECRET",
    "webhook_verify_ssl": 1
  }'
```

```json
{
  "result": true,
  "changed_keys": [
    "order_create_webhook_url",
    "order_status_change_webhook_url",
    "tracking_event_webhook_url",
    "pod_files_webhook_url",
    "order_cancel_failed_webhook_url",
    "webhook_sign_secret"
  ],
  "settings": {
    "webhook_sign_secret": "************CRET",
    "tracking_event_webhook_url": "https://erp.farineetfils.example/hooks/superroute",
    "webhook_verify_ssl": 1
  }
}
```

- `changed_keys`: nastavenia, ktoré toto volanie zmenilo.
- `settings.webhook_sign_secret`: vracia sa zamaskované; úplnú hodnotu uchovávajte iba na svojom serveri.

**GraphQL:** `webhookSettingsUpdate` ([Príručka GraphQL](/api/graphql/documentation#/webhooks/webhookSettingsUpdate)).

Na strane príjemcu overte podpis **v2** nad surovým telom: `HMAC_SHA256(timestamp + "." + raw_body, secret)` porovnaný s `X-Webhook-Signature-V2`, kde časová značka je `X-Webhook-Timestamp`. Duplicity odstraňujte podľa `X-Webhook-Event-Id`. Odpovedzte **2xx do 3 sekúnd** a udalosť spracujte až potom.

```php
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE_V2'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $raw, $sharedSecret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
```

**Overenie:** vytvorte jednu testovaciu objednávku a prijmite `order.created` s rovnakým `id` a `tracking_number`. Príjemca odmietne neplatný podpis kódom `401` a druhé doručenie rovnakého `X-Webhook-Event-Id` sa nespracuje dvakrát.

## 12. Zrušenie objednávky

Objednávku zrušte, keď je veľkoobchodná objednávka stiahnutá alebo vyzdvihnutie prepraviek už nie je potrebné. Volanie je idempotentné: zrušenie už zrušenej objednávky opäť uspeje.

**REST:** `POST /api/v1/orders/cancel` — [Príručka REST](/api/documentation#/paths/v1-orders-cancel/post) — pošlite práve jedno z `order_id`, `tracking_number`, `external_tracking_number`.

```bash
curl -X POST https://YOUR_HOST/api/v1/orders/cancel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tracking_number":"SR123456789012"}'
```

```json
{
  "result": true,
  "id": 12345,
  "message": "Order 12345 has been cancelled successful.",
  "already_cancelled": false
}
```

- `result`: `true`, keď je objednávka zrušená.
- `already_cancelled`: `true`, keď bola objednávka zrušená pred týmto volaním; považujte to za úspech.
- `code`: prítomné, keď je zrušenie odmietnuté; pozri krok 14.

**GraphQL:** `ordersCancel` ([Príručka GraphQL](/api/graphql/documentation#/orders/ordersCancel)). Výsledok je typovaný a vyžaduje selection set.

```graphql
query {
  ordersCancel(tracking_number: "SR123456789012") {
    result
    id
    message
    already_cancelled
    code
  }
}
```

**Overenie:** detail objednávky ukazuje `orders_status_id` `12` a rovnaké zrušenie vráti `already_cancelled: true`. Keď je zrušenie odmietnuté, na `order_cancel_failed_webhook_url` sa odošle `order.cancel_failed`.

## 13. Dávkové vytvorenie objednávok (voliteľne)

ERP môže odoslať veľkoobchodné objednávky a vyzdvihnutia prepraviek za celý deň v jednej požiadavke. Každý riadok prijíma rovnaké polia ako kroky 6 a 7 a môže mať `type` `D` alebo `P`.

**REST:** `POST /api/v1/client/batchOrderCreate` — [Príručka REST](/api/documentation#/paths/v1-client-batchOrderCreate/post) — odpovie, keď sú spracované všetky riadky.

```bash
curl -X POST https://YOUR_HOST/api/v1/client/batchOrderCreate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: batch-2026-10-01" \
  -d '{
    "per_order_transaction": 1,
    "orders": [
      {
        "type": "D",
        "need_pick_up": 0,
        "ref": "WHS-20932",
        "name": "Sophie Roy",
        "company_name": "Boulangerie du Marché",
        "telephone": "5145550163",
        "address_1": "7070 Avenue Henri-Julien",
        "city": "Montréal",
        "province": "QC",
        "postcode": "H2S3S3",
        "country": "Canada",
        "packages": 1,
        "packagesDetail": [{ "ref": "WHS-20932-1", "weight": 10, "weight_unit": 2, "length": 60, "width": 40, "height": 30, "dimension_unit": 2 }]
      },
      {
        "type": "P",
        "need_pick_up": 1,
        "ref": "CRT-20932",
        "name": "Luc Gagnon",
        "company_name": "Épicerie Wellington",
        "telephone": "5145550187",
        "address_1": "4100 Rue Wellington",
        "city": "Verdun",
        "province": "QC",
        "postcode": "H4G1V5",
        "country": "Canada",
        "packages": 1,
        "packagesDetail": [{ "ref": "CRT-20932-1", "weight": 4, "weight_unit": 2, "length": 60, "width": 40, "height": 30, "dimension_unit": 2 }]
      }
    ]
  }'
```

```json
[
  { "result": true, "id": 12347, "ref": "WHS-20932", "tracking_number": ["SR123456789036"], "packages": [{ "id": 67892, "ref": "WHS-20932-1", "tracking_number": "SR123456789036", "external_tracking_number": "" }] },
  { "result": true, "id": 12348, "ref": "CRT-20932", "tracking_number": ["SR123456789043"], "packages": [{ "id": 67893, "ref": "CRT-20932-1", "tracking_number": "SR123456789043", "external_tracking_number": "" }] }
]
```

- Každý riadok má vlastné `result`; priraďte ho k svojmu riadku objednávky podľa `ref`. Odmietnutý riadok nesie `message` a `skipped_ref` a môže niesť `code` (napríklad `INSUFFICIENT_BALANCE` alebo `OUT_OF_DELIVERY_AREA`).
- `per_order_transaction`: `1` potvrdí každý riadok samostatne, takže jeden neúspešný riadok nemôže vrátiť späť ostatné.
- Dávky s viac ako 100 objednávkami dostanú hlavičku odpovede `X-Batch-Size-Warning`; posielajte ich na asynchrónny endpoint.

**REST:** `POST /api/v1/client/batchOrderCreateAsync` — [Príručka REST](/api/documentation#/paths/v1-client-batchOrderCreateAsync/post) — prijíma rovnaké telo a okamžite vráti identifikátor úlohy:

```json
{ "message": "Order batch created in async,please check later.", "asyncId": 28 }
```

Dopytujte sa na `GET /api/v1/client/async/{id}` — [Príručka REST](/api/documentation#/paths/v1-client-async-id/get) — s `asyncId` alebo prijmite `order.create_async` na `order_create_async_postback_url`. Výsledkom úlohy je rovnaký zoznam po riadkoch ako pri synchrónnom endpointe.

```bash
curl https://YOUR_HOST/api/v1/client/async/28 \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

**GraphQL:** `clientBatchOrderCreate` ([Príručka GraphQL](/api/graphql/documentation#/client/clientBatchOrderCreate)), `clientBatchOrderCreateAsync` ([Príručka GraphQL](/api/graphql/documentation#/client/clientBatchOrderCreateAsync)) a `clientAsync` ([Príručka GraphQL](/api/graphql/documentation#/client/clientAsync)).

**Overenie:** dávka s dvoma riadkami vráti dva výsledky, každý s jeho `ref`. Asynchrónna úloha po spustení vráti rovnaké riadky.

## 14. Spracovanie chýb

| Situácia | Stav HTTP | Kód | Čo integrácia urobí |
|---|---|---|---|
| Povinné pole chýba alebo má nesprávny formát (vytvorenie) | 400 | `VALIDATION_FAILED` | Opravte pole uvedené v `message` a požiadavku odošlite znova. |
| Zostatok účtu nepokrýva objednávku | 400 | `INSUFFICIENT_BALANCE` | Prečítajte `insufficient_balance` (požadované, dostupné, chýbajúce); dobite zostatok a potom zopakujte. Žiadna objednávka nebola vytvorená. |
| Adresa je mimo oblasti služby a firma takéto objednávky odstraňuje | 400 | `OUT_OF_DELIVERY_AREA` | Zadajte adresu v oblasti služby. Žiadna objednávka nebola vytvorená. |
| `ref` balíka alebo externé sledovacie číslo už existuje (so zapnutou deduplikáciou) | 200 (`result` `false`), alebo 409 so `strict_duplicate_check` `1` | `DUPLICATE_TRACKING_NUMBER` | Prečítajte `exist_package_ref` a namiesto vytvorenia novej objednávky prepojte existujúcu. |
| `Idempotency-Key` je použitý znova s iným telom | 409 | `IDEMPOTENCY_CONFLICT` | Pre inú požiadavku použite nový kľúč. |
| Požiadavka s rovnakým `Idempotency-Key` ešte prebieha | 409 | `IDEMPOTENCY_IN_PROGRESS` | Počkajte a potom zopakujte s rovnakým kľúčom. |
| Zrušenie bez identifikátora objednávky | 400 | `MISSING_IDENTIFIER` | Pošlite jedno z `order_id`, `tracking_number`, `external_tracking_number`. |
| Zrušenie objednávky, ktorá neexistuje | 400 | `ORDER_NOT_FOUND` | Skontrolujte uložené `id` alebo sledovacie číslo. |
| Číslo zodpovedá viac ako jednej aktívnej objednávke | 409 | `MULTIPLE_ORDERS_MATCHED` | Zrušte podľa `order_id` s použitím jedného z `matched_order_ids`. |
| Objednávka patrí inému účtu | 401 | `ORDER_CANCEL_UNAUTHORIZED` | Zrušte účtom, ktorý objednávku vytvoril. |
| Stav objednávky už zrušenie nepovoľuje | 401 | `ORDER_STATUS_NOT_CANCELLABLE` | Objednávku ponechajte bez zmeny; vrátenie riešte samostatne. |
| Objednávku drží externý dopravca, ktorý ju nemôže zrušiť | 409 | `ORDER_CANCEL_BLOCKED_THIRD_PARTY`, `THIRD_PARTY_CANCEL_NOT_SUPPORTED` alebo `THIRD_PARTY_CANCEL_FAILED` | Objednávka je nezmenená; kontaktujte firmu. |
| Token chýba alebo vypršal, alebo účet nesmie zadávať objednávky | 401 | — | Prihláste sa znova; skontrolujte oprávnenia účtu. |

## Zoznam testov

Použite testovacie referencie, napríklad `WHS-20931` a `CRT-20931`:

- [ ] (Voliteľne) Cenová ponuka vráti cenu pre PSČ v oblasti s `type` `D`.
- [ ] (Voliteľne) Cenová ponuka vráti cenu pre PSČ v oblasti s `type` `P`.
- [ ] Vytvorenie doručenia vráti `id` + `tracking_number`; rovnaký `Idempotency-Key` nevytvorí druhú objednávku.
- [ ] Vytvorenie vyzdvihnutia vráti `id` + `tracking_number`; detail objednávky ukazuje `type` `P` a `need_pickup` `1`.
- [ ] Detail objednávky aj zoznam ukazujú obe objednávky pod týmto účtom.
- [ ] PDF miestneho štítku sa otvorí a ukazuje príjemcu alebo adresu vyzdvihnutia.
- [ ] Verejné sledovanie vráti časovú os bez tokenu; najnovšia udalosť je `100`.
- [ ] Príde `order.created` a jeho podpis v2 sa overí.
- [ ] Zrušenie vráti `result: true` a druhé zrušenie vráti `already_cancelled: true`.
- [ ] Dávka s jedným doručením a jedným vyzdvihnutím vráti dva výsledky, každý s jeho `ref`.
