# Skladovanie a výdaj

API skladovania a výdaja umožňuje zákazníckemu účtu skladovej firmy prihlásiť tovar na uskladnenie, zaplatiť obdobie skladovania a neskôr odoslať uskladnené balíky vlastným kupujúcim. Je určené pre obchodníkov a platformy, ktoré držia zásoby v sklade tretej strany (3PL) a potrebujú zo svojich systémov automatizovať rezerváciu skladovania, prehľad zásob a odchádzajúce zásielky. Všetky volania sa vykonávajú ako zákaznícky účet, nikdy ako skladová firma.

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

Príklady v tejto príručke sledujú jeden scenár. **Northwind Outdoor**, sezónny internetový predajca zimného vybavenia, skladuje svoje zimné zásoby v sklade **Toronto Hub** (sklad `7`) svojho poskytovateľa 3PL od 1. novembra 2026 do 31. marca 2027. Keď kupujúci objedná kartón zateplených búnd, Northwind odošle tento kartón zo zásob kupujúcemu v Ottawe.

- **Rezervácia sezónneho skladovania.** Administratíva predajcu získa cenovú ponuku a rezervuje obdobie skladovania pre každý prichádzajúci kartón ešte predtým, ako tovar opustí dodávateľa, a poplatok za skladovanie zaplatí zo zostatku svojho účtu.
- **Aktuálny prehľad zásob.** Obchod alebo ERP predajcu uvádza balíky, ktoré sklad skutočne prijal a ktoré sú ešte dostupné na odoslanie, takže na vybavenie sa ponúkajú iba skutočné zásoby.
- **Vybavenie objednávok zo zásob.** Keď kupujúci zadá objednávku, systém predajcu ocení odchádzajúcu zásielku, vytvorí požiadavku na výdaj uskladnených balíkov, zaplatí ju a zaznamená sledovacie číslo pre kupujúceho.
- **Sledovanie stavu a opravy.** Systém predajcu načítava stav každej objednávky skladovania a každého výdaja, sleduje zásielku cez verejné sledovanie a ruší výdaj, ktorý už nie je potrebný, kým je to ešte povolené.

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

Túto príručku použite, keď je tovar už uskladnený alebo bude uskladnený v sklade firmy a zásielka začína z týchto zásob. Tok je: prihlásenie → načítanie konfigurácie skladovania → cenová ponuka skladovania → vytvorenie objednávky skladovania → platba → zoznam balíkov na sklade → zoznam služieb a odhad výdaja → vytvorenie výdaja → platba → čítanie a sledovanie → webhooky → zrušenie.

Iné prípady pokrývajú iné príručky:

- **Uniorder: jedno API pre každú zásielku** — odporúčaný jediný vstupný bod (`/api/v1/uniorder/...`) pre nové integrácie, ktoré rezervujú miestne doručenie alebo štítky dopravcu. Uniorder **nepokrýva** skladovanie a výdaj; objednávky skladovania a výdaje sa vytvárajú iba cez zákaznícke endpointy v tejto príručke.
- **Prepravné služby** — zákazník odosiela tovar, ktorý nie je uskladnený, s použitím prepravných služieb firmy.
- **Štítky dopravcu** — firma kupuje štítky dopravcov priamo pre vlastné balíky.
- **Vyzdvihnutie a doručenie (vlastná flotila)** — firma rezervuje vyzdvihnutia a doručenia vlastnou flotilou.

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

- **Typ účtu.** **Zákaznícky** účet skladovej firmy (poskytovateľom služby je firma, ktorá prevádzkuje sklad). Token firemného (klientskeho) účtu na endpointoch `/api/v1/customer/...` nefunguje.
- **Oprávnenia.** Zákaznícky účet potrebuje prístup k API. Endpointy skladovania navyše vyžadujú funkciu skladovania; endpointy výdaja vyžadujú, aby firma pre tohto zákazníka zapla výdaj (alebo konsolidáciu), inak odpovedia `403`.
- **Zostatok.** Platby za skladovanie a výdaj sa strhávajú zo zostatku zákazníckeho účtu. Na test požiadajte firmu, aby pripísala prostriedky na zostatok testovacieho zákazníka.
- **Testovacie údaje.** `id` skladu, aspoň jedno `id` balenia, ak nie sú povolené vlastné balíky, a aspoň jedna aktívna prepravná služba dostupná z tohto skladu. Výdaj funguje až po tom, ako sklad uskladnené balíky **prijal**; pri teste požiadajte personál skladu, aby testovaciu objednávku skladovania prijal.
- **Zaobchádzanie s tokenom.** Prihlasujte sa zo svojho servera, token uchovávajte na serveri a nikdy ho nevkladajte do kódu prehliadača ani mobilnej aplikácie.
- **Zástupné hodnoty.** Nahraďte `YOUR_HOST` hostiteľom vašej platformy a `ACCESS_TOKEN` tokenom z kroku 4.

## 4. Prihlásenie ako zákazník

Každé ďalšie volanie sa autorizuje zákazníckym tokenom typu bearer. Vaša integrácia sa prihlási raz, uloží token na strane servera a obnoví ho pred `expires_at`.

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

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

```json
{
  "result": true,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi...",
  "token_type": "Bearer",
  "expires_at": "2027-09-28 10:15:00",
  "expires_timestamp": 1822040100,
  "name": "Northwind Outdoor"
}
```

- `access_token` — posielajte ho pri každej požiadavke v hlavičke uvedenej nižšie.
- `expires_at` / `expires_timestamp` — pred týmto časom sa prihláste znova.

```
Authorization: Bearer ACCESS_TOKEN
```

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

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

## 5. Načítanie konfigurácie skladovania

Balík konfigurácie uvádza sklady, ktoré môže zákazník používať, katalóg balení, jednotky a príplatky. Vaša integrácia ho načíta raz za reláciu, aby vybrala sklad a zostavila platné riadky balíkov.

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

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

```json
{
  "result": true,
  "data": {
    "warehouses": [
      { "id": 7, "name": "Toronto Hub", "address": "10 Main St", "city": "Toronto", "province": "ON", "postcode": "M5V 2T6" }
    ],
    "packagings": [
      { "id": 1, "name": "Large Carton", "type": "Box", "length": 60, "width": 40, "height": 40, "dimension_unit": 2 }
    ],
    "dimension_units": { "1": { "name": "dimension_mm" }, "2": { "name": "dimension_cm" }, "3": { "name": "dimension_m" }, "4": { "name": "dimension_inch" } },
    "weight_units": { "1": { "name": "weight_g" }, "2": { "name": "weight_kg" }, "3": { "name": "weight_oz" }, "4": { "name": "weight_lb" } },
    "allow_custom_package": true,
    "surcharges": [],
    "form_bindings": []
  }
}
```

- `warehouses[].id` — `warehouse_id` pre každé ďalšie volanie.
- `allow_custom_package` — keď je `false`, každá položka skladovania musí niesť `packaging_id` z `packagings[]`; keď je `true`, položky možno opísať iba rozmermi.
- `dimension_units` / `weight_units` — celočíselné kódy používané v riadkoch balíkov (`2` = cm, `2` = kg).
- `form_bindings` — formuláre, ktoré firma vyžaduje pri objednávke skladovania; ich odpovede pošlite ako `form_data` v kroku 7.

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

```graphql
query {
  customerStorageOrderConfig
}
```

**Overenie:** zaznamenali ste `id` skladu a, ak katalóg nie je prázdny, `id` balenia.

## 6. Cenová ponuka obdobia skladovania

Cenová ponuka ocení obdobie skladovania pre plánované balíky ešte pred akoukoľvek rezerváciou. Vaša integrácia túto cenu zobrazí alebo skontroluje a potom vytvorí objednávku s rovnakými vstupmi.

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

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders/calculate-price \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "warehouse_id": 7,
    "start_date": "2026-11-01",
    "end_date": "2027-03-31",
    "items": [{
      "qty": 2,
      "length": 60,
      "width": 40,
      "height": 40,
      "dimension_unit": 2,
      "weight": 14,
      "weight_unit": 2
    }]
  }'
```

```json
{
  "success": true,
  "price": {
    "total_price": "186.45",
    "currency": "CAD",
    "shipping_price": 186.45
  }
}
```

- `success` — `true`, keď bola cena vypočítaná.
- `price.total_price` / `price.currency` — cena skladovania za obdobie vrátane dane.
- `promotion` — prítomné iba vtedy, keď sa uplatní akcia.

**Overenie:** `success` alebo `result` je true a máte cenu. Chýbajúce `warehouse_id` / dátumy vrátia `400`.

## 7. Vytvorenie objednávky skladovania

Objednávka skladovania ohlási skladu prichádzajúce balíky a stanoví obdobie skladovania. Vaša integrácia uloží vrátené id; je potrebné na platbu, načítanie objednávky a jej zrušenie.

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

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: northwind-winter-2026-po-4471" \
  -d '{
    "warehouse_id": 7,
    "start_date": "2026-11-01",
    "end_date": "2027-03-31",
    "notes": "Winter 2026 stock, PO 4471",
    "items": [{
      "description": "Insulated jackets, carton of 12",
      "qty": 2,
      "length": 60,
      "width": 40,
      "height": 40,
      "dimension_unit": 2,
      "weight": 14,
      "weight_unit": 2,
      "value": 1800
    }]
  }'
```

```json
{
  "result": true,
  "message": "Storage order created",
  "data": { "id": 1024, "status": "pending payment" }
}
```

- `data.id` — id objednávky skladovania. Uložte ho k svojej nákupnej objednávke.
- `data.status` — `pending payment`, kým objednávka nie je zaplatená.
- `Idempotency-Key` — odvoďte ho od vlastného stabilného id. Opakovaný kľúč s rovnakým telom vráti prvú odpoveď (`replayed: true`); rovnaký kľúč s iným telom sa odmietne s `409 IDEMPOTENCY_CONFLICT`.
- Povinné polia: `warehouse_id`, `start_date`, `end_date` (po `start_date`) a `items[]` s `qty`, `length`, `width`, `height`, `dimension_unit`. Keď je `allow_custom_package` `false`, pridajte `items[].packaging_id`.

**Overenie:** odpoveď obsahuje `data.id`. Toto id objednávky skladovania uložte.

## 8. Platba za skladovanie

Platba potvrdí objednávku skladovania. Vaša integrácia môže najprv načítať splatnú sumu a potom zaplatiť zo zostatku zákazníka.

**REST:** `GET /api/v1/customer/storage-orders/{id}/payment-info` — [Príručka REST](/api/documentation#/paths/v1-customer-storage-orders-id--payment-info/get) (voliteľné)

```bash
curl https://YOUR_HOST/api/v1/customer/storage-orders/1024/payment-info \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

```json
{
  "success": true,
  "result": true,
  "data": {
    "order_id": 1024,
    "currency": "CAD",
    "total_price": "186.45",
    "paid_amount": "0.00",
    "remaining_balance": "186.45",
    "user_balance": "500.00",
    "has_sufficient_balance": true,
    "minimum_payment": "186.45"
  }
}
```

**REST:** `POST /api/v1/customer/storage-orders/{id}/pay` — [Príručka REST](/api/documentation#/paths/v1-customer-storage-orders-id--pay/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/storage-orders/1024/pay \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"payment_type": "full_balance"}'
```

```json
{
  "success": true,
  "result": true,
  "message": "Payment of $186.45 processed successfully. Your storage order is now confirmed.",
  "new_balance": 313.55,
  "paid_amount": 186.45,
  "charge_amount": 186.45,
  "is_fully_paid": true
}
```

- `payment_type` — `full_balance` (predvolené, zaplatí zostávajúcu sumu), `minimum_payment` (zaplatí minimum, ktoré firma vyžaduje) alebo `custom` spolu s `custom_amount`.
- `is_fully_paid` — `true`, keď nezostáva nič na zaplatenie.
- Nedostatočný zostatok vráti `400` s `customer_balance`; dobite zostatok a potom zopakujte.

Načítajte objednávku, aby ste potvrdili jej stav a neskôr aj to, ktoré balíky sklad prijal.

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

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

```json
{
  "result": true,
  "data": {
    "id": 1024,
    "status": "confirmed",
    "can_cancel": true,
    "store_from": "2026-11-01",
    "store_to": "2027-03-31",
    "warehouse_id": 7,
    "packages": [
      { "id": 5001, "description": "Insulated jackets, carton of 12", "quantity": 1, "received": false },
      { "id": 5002, "description": "Insulated jackets, carton of 12", "quantity": 1, "received": false }
    ],
    "total_price": 186.45,
    "currency": "CAD"
  }
}
```

- `status` — `confirmed` po platbe; neskôr `partial received` / `storage in progress` podľa príchodu tovaru.
- `packages[].received` — `true`, keď sklad daný balík prijal.
- `can_cancel` — či možno objednávku skladovania ešte zrušiť.

**GraphQL:** `customerStorageOrderShow` ([Príručka GraphQL](/api/graphql/documentation#/customer/customerStorageOrderShow)); zoznam všetkých objednávok skladovania je `customerStorageOrders` ([Príručka GraphQL](/api/graphql/documentation#/customer/customerStorageOrders)).

```graphql
query {
  customerStorageOrderShow(id: 1024) {
    result
    data {
      id
      status
      can_cancel
      packages { id description received }
    }
  }
}
```

**Overenie:** objednávka skladovania je zaplatená / potvrdená. `400` s `customer_balance` znamená, že treba dobiť zostatok a potom zopakovať.

Výdaj uvedený nižšie funguje až po tom, ako sú balíky v sklade **prijaté**. Pri teste počkajte, kým ich personál (alebo testovací príjem) označí ako prijaté, a potom pokračujte.

## 9. Zoznam položiek, ktoré sú ešte na sklade

Tento zoznam predstavuje zásoby, ktoré môže vaša integrácia odoslať. Obsahuje iba balíky, ktoré sklad prijal a ktoré ešte nie sú viazané na iný výdaj.

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

```bash
curl "https://YOUR_HOST/api/v1/customer/shipout-orders/available-items?warehouse_id=7" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

```json
{
  "result": true,
  "data": {
    "warehouses": [{ "id": 7, "name": "Toronto Hub", "available_count": 2 }],
    "storage_orders": [{
      "id": 1024,
      "warehouse_id": 7,
      "packages": [
        { "id": 5001, "description": "Insulated jackets, carton of 12", "weight": 14, "weight_unit": 2, "length": 60, "width": 40, "height": 40, "dimension_unit": 2, "value": 1800 },
        { "id": 5002, "description": "Insulated jackets, carton of 12", "weight": 14, "weight_unit": 2, "length": 60, "width": 40, "height": 40, "dimension_unit": 2, "value": 1800 }
      ]
    }]
  }
}
```

- `storage_orders[].packages[].id` — `storage_package_ids` na výdaj v kroku 10.
- `warehouses[].available_count` — počet dostupných balíkov v každom sklade.

**GraphQL:** `customerShipoutAvailableItems` ([Príručka GraphQL](/api/graphql/documentation#/storage-shipout/customerShipoutAvailableItems))

```graphql
query {
  customerShipoutAvailableItems(warehouse_id: 7) {
    result
    data {
      warehouses { id name available_count }
      storage_orders { id warehouse_id packages { id description weight length width height } }
    }
  }
}
```

**Overenie:** zaznamenali ste jedno alebo viac `storage_package_ids` (príklad `5001`). Prázdny zoznam znamená, že zatiaľ nebolo nič prijaté — výdaj nevytvárajte. `403` znamená, že výdaj je pre tohto zákazníka vypnutý.

## 10. Odhad a vytvorenie výdaja

Výdaj sa oceňuje prepravnou službou firmy. Vaša integrácia načíta služby dostupné zo skladu, odhadne cenu pre cieľ kupujúceho a potom vytvorí výdaj pre vybrané balíky.

**REST:** `GET /api/v1/customer/shipout-orders/services?warehouse_id=7` — [Príručka REST](/api/documentation#/paths/v1-customer-shipout-orders-services/get)

```bash
curl "https://YOUR_HOST/api/v1/customer/shipout-orders/services?warehouse_id=7" \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

```json
{
  "result": true,
  "data": {
    "services": [
      { "id": 5, "service_code": "intl_express", "name": { "en": "Express" }, "pricing_method": 1, "pricing_method_name": "Shipping Price Plan", "support_multi_package": true }
    ]
  }
}
```

Zaznamenajte `service_code`.

**REST:** `POST /api/v1/customer/shipout-orders/services/{serviceCode}/estimate` — [Príručka REST](/api/documentation#/paths/v1-customer-shipout-orders-services-serviceCode--estimate/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/services/intl_express/estimate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "warehouse_id": 7,
    "delivery_postcode": "K2P1L4",
    "delivery_country": "CA",
    "packages": [{
      "weight": 14,
      "length": 60,
      "width": 40,
      "height": 40,
      "weight_unit": 2,
      "dimension_unit": 2
    }]
  }'
```

```json
{
  "result": true,
  "data": {
    "shipping_fee": 38.5,
    "fuel_surcharge": 4.2,
    "handling_fee": 0,
    "sub_total": 42.7,
    "tax": 5.55,
    "total": 48.25,
    "currency": "CAD",
    "has_items_needing_quote": false,
    "refused": false
  }
}
```

- `total` / `currency` — odhadovaná cena pre tento cieľ.
- `has_items_needing_quote` — `true`, keď sa služba oceňuje ručne; sklad stanoví cenu po vytvorení výdaja a platba na ňu čaká.
- `refused` / `refusal_message` — služba túto zásielku neprijme, pretože ju nedokáže oceniť.

**REST:** `POST /api/v1/customer/shipout-orders/services/{serviceCode}/orders` — [Príručka REST](/api/documentation#/paths/v1-customer-shipout-orders-services-serviceCode--orders/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/services/intl_express/orders \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: northwind-order-NW-20931" \
  -d '{
    "warehouse_id": 7,
    "storage_package_ids": [5001],
    "delivery_name": "Maya Chen",
    "delivery_telephone": "6135550142",
    "delivery_email": "maya.chen@example.com",
    "delivery_address_1": "150 Elgin St",
    "delivery_city": "Ottawa",
    "delivery_province": "ON",
    "delivery_country": "CA",
    "delivery_postcode": "K2P1L4",
    "note": "Web order NW-20931"
  }'
```

```json
{
  "result": true,
  "data": {
    "id": 8001,
    "status": 0,
    "is_storage_shipout": true,
    "total_price": "48.25",
    "price_breakdown": { "total": 48.25, "has_items_needing_quote": false },
    "has_items_needing_quote": false,
    "storage_package_ids": [5001]
  }
}
```

- `data.id` — id výdaja. Uložte ho k objednávke kupujúceho.
- `data.status` — `0` = čakajúci (čaká na platbu), `1` = potvrdený, `2` = na ceste, `3` = odoslaný, `4` = zrušený, `5` = neúspešný.
- `storage_package_ids` — tieto balíky sú teraz viazané na tento výdaj a v kroku 9 sa už nezobrazujú.
- Povinné polia: `warehouse_id`, `storage_package_ids`, `delivery_name`, `delivery_telephone`, `delivery_address_1`, `delivery_city`, `delivery_province`, `delivery_country`, `delivery_postcode`. Všetky balíky musia pochádzať z toho istého skladu.

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

```graphql
mutation {
  customerCreateShipoutOrder(
    service_code: "intl_express"
    warehouse_id: 7
    storage_package_ids: [5001]
    delivery_name: "Maya Chen"
    delivery_telephone: "6135550142"
    delivery_email: "maya.chen@example.com"
    delivery_address_1: "150 Elgin St"
    delivery_city: "Ottawa"
    delivery_province: "ON"
    delivery_country: "CA"
    delivery_postcode: "K2P1L4"
    note: "Web order NW-20931"
  ) {
    result
    message
    data { id status total_price has_items_needing_quote storage_package_ids }
  }
}
```

**Overenie:** odpoveď obsahuje `id` výdaja. Vybrané uskladnené balíky sú viazané na túto požiadavku.

## 11. Platba za výdaj

Sklad spracuje výdaj po jeho zaplatení. Vaša integrácia zaplatí zostávajúcu sumu z účtu zákazníka; ak chcete zaplatiť celú sumu, vynechajte `amount`.

**REST:** `POST /api/v1/customer/shipout-orders/{id}/pay` — [Príručka REST](/api/documentation#/paths/v1-customer-shipout-orders-id--pay/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/8001/pay \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```json
{
  "result": true,
  "data": {
    "transaction_id": 9001,
    "amount": "48.25",
    "order_status": 1,
    "paid_amount": 48.25,
    "remaining_balance": 0
  }
}
```

- `amount` (požiadavka, voliteľné) — čiastková suma; predvolene celá zostávajúca suma.
- `order_status` — `1` (potvrdený) po úplnej platbe.
- `remaining_balance` — `0` pri úplnom zaplatení.

**GraphQL:** `customerPayShipout` ([Príručka GraphQL](/api/graphql/documentation#/storage-shipout/customerPayShipout))

```graphql
mutation {
  customerPayShipout(id: 8001) {
    result
    message
    data { transaction_id amount order_status paid_amount remaining_balance }
  }
}
```

**Overenie:** platba zaznamená sumu (alebo vráti `402` / `422` s jasným dôvodom). `402` znamená nedostatočný zostatok; `422` znamená, že objednávku ešte nemožno zaplatiť (napríklad stále čaká na ručnú cenovú ponuku) alebo je suma neplatná.

## 12. Čítanie a sledovanie výdaja

Vaša integrácia načítava výdaj, aby sledovala jeho stav, a po odoslaní zo skladu sleduje zásielku podľa jej sledovacieho čísla.

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

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

```json
{
  "result": true,
  "data": {
    "id": 8001,
    "status": 3,
    "status_label": "Shipped",
    "warehouse": { "id": 7, "name": "Toronto Hub" },
    "shipping_service": { "id": 5, "service_code": "intl_express" },
    "total_price": "48.25",
    "paid_amount": 48.25,
    "remaining_balance": 0,
    "can_be_paid": false,
    "can_be_cancelled": false,
    "storage_packages": [{ "id": 5001, "storage_order_id": 1024, "description": "Insulated jackets, carton of 12" }]
  }
}
```

- `status` / `status_label` — aktuálny stav výdaja.
- `can_be_paid` / `can_be_cancelled` — či je krok 11 alebo krok 14 momentálne povolený.

Keď existuje sledovacie číslo:

**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,
  "deliveried": false,
  "data": [
    { "tracking_event_status_id": 3, "description": "Package picked up", "location_city": "Toronto", "updated_at": "2026-12-02 14:30:00" }
  ]
}
```

- `data[]` — sledovacie udalosti v chronologickom poradí.
- `deliveried` — `true` po doručení zásielky.

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

```graphql
query {
  trackingPublic(trackingNumber: "SR123456789012") {
    result
    deliveried
    data { tracking_event_status_id description location_city updated_at }
  }
}
```

**Overenie:** načítanie výdaja vráti očakávaný `status`. Verejné sledovanie nájde zásielku, keď existuje číslo.

## 13. Odber webhookov

Webhooky posielajú zmeny sledovania a stavu na váš server namiesto opakovaného dopytovania. Zákaznícky účet si nastavuje vlastné URL webhookov a podpisové tajomstvo; nastavenia sa ukladajú k zákazníckemu účtu, nie k firme.

**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 '{
    "tracking_event_webhook_url": "https://hooks.northwind-outdoor.example/tracking",
    "order_status_change_webhook_url": "https://hooks.northwind-outdoor.example/status",
    "webhook_sign_secret": "nw-webhook-secret-2026-0123456789"
  }'
```

```json
{
  "result": true,
  "changed_keys": ["tracking_event_webhook_url", "order_status_change_webhook_url", "webhook_sign_secret"],
  "recipient_type": "customer",
  "settings": {
    "webhook_sign_secret": "************6789",
    "tracking_event_webhook_url": "https://hooks.northwind-outdoor.example/tracking",
    "order_status_change_webhook_url": "https://hooks.northwind-outdoor.example/status"
  }
}
```

- Menia sa iba odoslané kľúče; neznámy kľúč alebo neplatná URL vráti `400`.
- `recipient_type` — `customer` potvrdzuje, že nastavenia patria zákazníckemu účtu.
- `webhook_sign_secret` — 16 až 255 znakov; uložte ho na svojom serveri na overovanie podpisov.

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

Overte **v2**: `HMAC_SHA256(timestamp + "." + raw_body, secret)` voči `X-Webhook-Signature-V2`. Duplicity odstraňujte podľa `X-Webhook-Event-Id`. Odpovedzte **2xx do 3 sekúnd**.

```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:** aktualizácia vráti `changed_keys` s odoslanými kľúčmi a testovacia udalosť prijatá na vašej URL prejde vyššie uvedenou kontrolou podpisu.

## 14. Zrušenie výdaja alebo objednávky skladovania

Zrušenie uvoľní to, čo bolo rezervované. Zrušenie výdaja vráti jeho balíky do zásob; zrušenie objednávky skladovania ukončí rezerváciu, ktorej tovar ešte nebol prijatý.

**REST:** `POST /api/v1/customer/shipout-orders/{id}/cancel` — [Príručka REST](/api/documentation#/paths/v1-customer-shipout-orders-id--cancel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipout-orders/8001/cancel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Buyer cancelled web order NW-20931"}'
```

```json
{ "result": true, "message": "Shipout cancelled." }
```

- `reason` (voliteľné) — zaznamená sa spolu so zrušením.
- Výdaj možno zrušiť iba vtedy, keď je čakajúci (`0`) alebo potvrdený (`1`).

**GraphQL:** `customerCancelShipout` ([Príručka GraphQL](/api/graphql/documentation#/storage-shipout/customerCancelShipout))

```graphql
mutation {
  customerCancelShipout(id: 8001, reason: "Buyer cancelled web order NW-20931") {
    result
    message
  }
}
```

Tým sa uvoľní viazanie uskladnených balíkov. Samotné skladovanie sa ruší pomocou `POST /api/v1/customer/storage-orders/{id}/cancel`, kým je to ešte povolené (stav `pending payment`, `confirmed`, `waiting for pickup` alebo `awaiting dropoff`; [Príručka REST](/api/documentation#/paths/v1-customer-storage-orders-id--cancel/post)).

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

```json
{ "result": true, "message": "Storage order cancelled", "data": { "id": 1024, "status": "cancelled" } }
```

- Suma už zaplatená za objednávku skladovania sa pripíše späť na zostatok zákazníka.
- Objednávka skladovania v akomkoľvek inom stave vráti `403`.

**Overenie:** `422` znamená, že tento stav nemožno zrušiť. Po úspešnom zrušení výdaja krok 9 znova uvádza balíky.

## 15. Spracovanie chýb

| Situácia | Stav HTTP | Kód | Čo integrácia urobí |
|---|---|---|---|
| Chýbajúci alebo expirovaný token, alebo nesprávny typ účtu | 401 | — | Prihláste sa znova ako zákazník (krok 4). |
| Cenová ponuka skladovania bez `warehouse_id` alebo dátumov | 400 | — | Pošlite `warehouse_id`, `start_date` a `end_date`. |
| Validácia objednávky skladovania zlyhala (chýbajúce rozmery položky, `end_date` nie je po `start_date`, chýbajúce `packaging_id`) | 422 | — | Prečítajte `errors`, opravte polia a odošlite znova. |
| Platba za skladovanie s nedostatočným zostatkom alebo objednávka je už úplne zaplatená | 400 | — | Dobite zostatok (odpoveď obsahuje `customer_balance`) alebo skončite, ak je už zaplatené. |
| Platba za skladovanie s `custom_amount` mimo povoleného rozsahu | 422 | — | Zaplaťte sumu medzi minimom a zostávajúcou sumou. |
| Objednávku skladovania nemožno v jej aktuálnom stave zrušiť | 403 | — | Požiadajte sklad o vybavenie objednávky; neopakujte. |
| Výdaj je pre tohto zákazníka vypnutý | 403 | — | Požiadajte firmu o zapnutie výdaja pre zákaznícky účet. |
| Neznámy kód služby alebo výdaj / objednávka skladovania sa nenašla | 404 | — | Znova načítajte zoznam služieb alebo skontrolujte uložené id. |
| Balík nie je dostupný, balíky sú z rôznych skladov alebo služba sa zo skladu neponúka | 422 | — | Znova načítajte krok 9 a vyberte dostupné balíky z jedného skladu. |
| Prepravná služba nedokáže zásielku oceniť a odmieta ju | 422 | `unpriced_refused` | Zvoľte inú službu alebo cieľ; nič sa nevytvorilo. |
| Platba za výdaj s nedostatočným zostatkom | 402 | — | Dobite zostatok a potom zopakujte krok 11. |
| Výdaj ešte nemožno zaplatiť (čaká na ručnú cenovú ponuku) alebo je suma neplatná | 422 | — | Počkajte na cenu, znova načítajte výdaj a potom zaplaťte. |
| Výdaj nemožno v jeho aktuálnom stave zrušiť | 422 | — | Zásielka je už v procese; neopakujte. |
| Rovnaký `Idempotency-Key` odoslaný s iným telom | 409 | `IDEMPOTENCY_CONFLICT` | Pre inú požiadavku použite nový kľúč. |
| Pôvodná požiadavka s rovnakým `Idempotency-Key` sa ešte spracúva | 409 | `IDEMPOTENCY_IN_PROGRESS` | Počkajte počet sekúnd z `Retry-After` a odošlite rovnakú požiadavku znova. |

## Zoznam testov

- [ ] Konfigurácia skladovania vráti `id` skladu.
- [ ] Cenová ponuka skladovania vráti cenu a vytvorenie skladovania vráti `data.id`.
- [ ] Platba za skladovanie uspeje, **alebo** ste overili, že peňaženku treba dobiť.
- [ ] Zoznam dostupných položiek uvádza prijaté balíky (`storage_package_ids`).
- [ ] Odhad výdaja vráti cenu alebo `has_items_needing_quote` a vytvorenie výdaja vráti `id` a viaže tieto balíky.
- [ ] Platba za výdaj uspeje (alebo rozumiete významu `402` / `422`).
- [ ] Verejné sledovanie nájde zásielku, keď existuje sledovacie číslo.
- [ ] Zrušenie výdaja uvoľní balíky, **alebo** tento stav nemožno zrušiť.
- [ ] Zopakovanie vytvorenia s rovnakým `Idempotency-Key` a telom vráti `replayed: true` a žiadnu druhú objednávku.
- [ ] Nastavenia webhookov vrátia `recipient_type: customer` a prijatá udalosť prejde kontrolou podpisu v2.
