# Prepravné služby

API prepravných služieb umožňuje zákazníckemu účtu rezervovať prepravné služby, ktoré jeho logistický poskytovateľ nastavil a priradil mu. Vlastný systém zákazníka načíta služby, ktoré môže používať, načíta pravidlá jednej služby, ocení zásielku, vytvorí prepravnú objednávku, zaplatí ju zo zostatku účtu a sleduje zásielku až do doručenia. Táto príručka je určená vývojárom, ktorí pripájajú systém dovozcu, obchodníka alebo veľkoobchodu k logistickému poskytovateľovi, ktorý ho obsluhuje.

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

Všetky príklady v tejto príručke používajú jeden scenár. **Harbourline Imports Inc.**, dovozca čaju v Toronte, má zákaznícky účet u svojho logistického poskytovateľa. Poskytovateľ ponúka službu `intl_express` (International Express) zo svojho skladu Toronto Hub (id skladu `7`). Harbourline odovzdá v Toronto Hub dva kartóny vzoriek čaju pre distribútora v Seattli pod svojou nákupnou objednávkou `HLI-PO-1058`.

- **Rezervácia zo systému nákupných objednávok.** Keď je nákupná objednávka uvoľnená, systém Harbourline ocení zásielku v službe `intl_express`, vytvorí prepravnú objednávku s číslom nákupnej objednávky ako referenciou a zaplatí ju z predplateného zostatku účtu bez toho, aby niekto otváral portál poskytovateľa.
- **Kontrola ceny pred záväzkom.** Nákupca v Harbourline vidí prepravné, príplatky, daň a celkovú sumu za dva kartóny ešte pred rezerváciou zásielky a zásielka, ktorú služba nedokáže oceniť, sa zastaví skôr, ako objednávka vznikne.
- **Stav zásielky v ERP.** Sledovacie číslo každého kartónu sa uloží k nákupnej objednávke; webhooky prenášajú stav objednávky a časovú os sledovania do ERP a nočná úloha vykonáva odsúhlasenie so zoznamom objednávok.
- **Kontrolované zmeny.** Nezaplatená rezervácia sa opraví priamo a rezervácia, ktorá už nie je potrebná, sa zruší a zaplatená suma sa vráti do kreditu účtu.

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

Túto skupinu použite, keď je volajúci **zákazníkom** logistickej firmy a rezervuje jednu z vlastných prepravných služieb firmy: firma nastavuje cenový plán, sklady, príplatky a balenie a služby zákazníkovi priraďuje. Zákazník vidí a rezervuje iba služby, ktoré sú mu priradené.

V týchto prípadoch použite inú skupinu:

- Volajúci je samotná logistická firma (firemný/klientsky účet) a rezervuje vyzdvihnutia a doručenia v ten istý deň alebo miestne vyzdvihnutia a doručenia vlastnou flotilou: pozrite príručku **Vyzdvihnutie a doručenie (vlastná flotila)**.
- Volajúci kupuje štítky dopravcov (napríklad UPS alebo FedEx) za dohodnuté sadzby účtu: pozrite príručku **Štítky dopravcu**.
- Zákazník skladuje tovar v sklade poskytovateľa a odosiela ho zo zásob: pozrite príručku **Skladovanie a výdaj**.

**Uniorder: jedno API pre každú zásielku** (`/api/v1/uniorder/...`) je odporúčaný jediný vstupný bod pre nové integrácie miestneho doručenia a štítkov dopravcu. Uniorder nepokrýva prepravné služby: objednávky prepravných služieb sa vytvárajú a spravujú iba cez endpointy `/api/v1/customer/shipping-orders/...` opísané tu.

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

- **Typ účtu.** **Zákaznícky** účet logistickej firmy s **oprávnením na API**, ktoré zapla firma. Firemný/klientsky ani zamestnanecký účet sa nemôže prihlásiť cez zákaznícke prihlásenie uvedené nižšie.
- **Priradenie služieb.** Firma musí zákazníkovi priradiť aspoň jednu aktívnu prepravnú službu. Zákazník bez priradenej služby dostane prázdny zoznam služieb.
- **Testovacie údaje.** Dohodnite sa s firmou na testovacom kóde služby, testovacom sklade a malom predplatenom zostatku na testovacom účte. Použite referenciu, napríklad `HLI-PO-1058` alebo `DEV-SHIP-001`, aby sa testovacie objednávky dali ľahko nájsť a zrušiť.
- **Zaobchádzanie s tokenom.** API volajte iba zo svojho servera. Heslo ani prístupový token nevkladajte do prehliadačov ani mobilných klientov. Token vyprší jeden týždeň po prihlásení (`expires_at`); prihláste sa znova pred jeho vypršaním.
- **Zástupné hodnoty.** Nahraďte `YOUR_HOST` názvom hostiteľa logistickej firmy a `ACCESS_TOKEN` tokenom vráteným v kroku prihlásenia.
- **Chyby v JSON.** Pri každej požiadavke posielajte `Accept: application/json`, aby chyby validácie vracali JSON namiesto presmerovania.

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

Prihlásenie vymení e-mail a heslo zákazníka za token typu bearer. Každé ďalšie volanie v tejto príručke tento token posiela.

**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" \
  -H "Accept: application/json" \
  -d '{"email":"logistics@harbourline-imports.example","password":"your_password"}'
```

```json
{
  "result": true,
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOi...",
  "token_type": "Bearer",
  "expires_at": "2026-10-05 09:15:00",
  "expires_timestamp": 1791206100,
  "name": "Harbourline Imports Inc."
}
```

- `access_token`: posielajte ho pri každej požiadavke ako `Authorization: Bearer ACCESS_TOKEN`. GraphQL používa rovnakú hlavičku na `POST /api/graphql`.
- `expires_at` / `expires_timestamp`: nové prihlásenie naplánujte pred týmto časom.

**Overenie:** odpoveď obsahuje `result: true` a `access_token`. Požiadavka bez tokenu vráti `401`; prihlásenie účtom, ktorý nie je zákaznícky alebo nemá oprávnenie na API, tiež vráti `401`.

## 5. Zoznam služieb priradených zákazníkovi

Zoznam služieb informuje integráciu, ktoré kódy služieb môže rezervovať a či každá služba prijíma odovzdanie v sklade, vyzdvihnutie alebo oboje. Uložte `service_code`; používa ho každé ďalšie volanie služby.

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

```bash
curl https://YOUR_HOST/api/v1/customer/shipping-orders/services \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json"
```

```json
{
  "result": true,
  "data": {
    "services": [
      {
        "id": 12,
        "service_code": "intl_express",
        "name": { "en": "International Express" },
        "offer_pickup": true,
        "allow_warehouse_delivery": true,
        "support_multi_package": true,
        "allow_special_requirements": false,
        "allow_purchase_supplies": true,
        "send_confirmation_email": true,
        "warehouses": [{ "id": 7, "name": "Toronto Hub" }]
      }
    ]
  }
}
```

- `service_code`: parameter cesty každého ďalšieho volania služby.
- `offer_pickup` / `allow_warehouse_delivery`: povolené hodnoty `origin_type` (`pickup` / `warehouse`).
- `support_multi_package`: či jedna objednávka môže obsahovať viac ako jeden riadok balíkov.
- Prázdne pole `services` znamená, že tomuto zákazníkovi nie je priradená žiadna služba.

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

```graphql
query CustomerShippingOrderServices {
  customerShippingOrderServices
}
```

**Overenie:** zoznam obsahuje aspoň jednu službu a uložili ste jej `service_code` (v tejto príručke: `intl_express`).

## 6. Načítanie konfigurácie služby

Konfigurácia vráti všetko, čo potrebuje formulár objednávky jednej služby: sklady, ktoré prijímajú odovzdanie, voliteľné príplatky, katalóg balenia a spotrebného materiálu, jednotky a krajiny, z ktorých služba môže vyzdvihovať a do ktorých môže doručovať. Pred ocenením alebo vytvorením čohokoľvek podľa nej overte údaje objednávky.

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

```bash
curl https://YOUR_HOST/api/v1/customer/shipping-orders/services/intl_express/config \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json"
```

```json
{
  "result": true,
  "data": {
    "service": {
      "service_code": "intl_express",
      "offer_pickup": true,
      "allow_warehouse_delivery": true,
      "allow_special_requirements": false,
      "allow_purchase_supplies": true,
      "weight_mode": 2,
      "volumetric_factor": 5000
    },
    "warehouses": [
      {
        "id": 7,
        "name": "Toronto Hub",
        "address_1": "250 Dundas St W",
        "city": "Toronto",
        "province": "ON",
        "country": "CA",
        "postcode": "M5T 2Z5",
        "telephone": "4165550100"
      }
    ],
    "surcharges": [],
    "has_surcharges": false,
    "packagings": [],
    "products": [],
    "weight_units": { "2": { "name": "weight_kg", "accuracy": 3 } },
    "dimension_units": { "2": { "name": "dimension_cm", "accuracy": 1 } },
    "delivery_allowed_countries": ["CA", "US"],
    "pickup_allowed_countries": ["CA"]
  }
}
```

- `warehouses[].id`: `warehouse_id`, ktoré sa posiela, keď je `origin_type` `warehouse`. Id, ktoré nie je v tomto zozname, sa pri vytvorení odmietne.
- `service.weight_mode`: ktoré polia balíka cenový plán vyžaduje: `0` skutočná hmotnosť (hmotnosť), `1` objemová hmotnosť (dĺžka, šírka a výška), `2` účtovaná hmotnosť (oboje). `null` znamená, že služba sa oceňuje ručne. Aby ste splnili každý režim, pošlite hmotnosť aj všetky tri rozmery.
- `delivery_allowed_countries` / `pickup_allowed_countries`: krajinu doručenia alebo vyzdvihnutia mimo týchto zoznamov odmietnite ešte pred volaním odhadu.
- `surcharges[].id`, `packagings[].id`, `products[].id`: id pre voliteľné príplatky, balenie a nákup spotrebného materiálu.
- `weight_units` / `dimension_units`: jednotky balíkov sa posielajú ako čísla. Posielajte `weight_unit: 2` (kg) a `dimension_unit: 2` (cm), ako to robia všetky príklady v tejto príručke; obe hodnoty sú zároveň predvolené, ak sa polia vynechajú.

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

```graphql
query CustomerShippingOrderServiceConfig {
  customerShippingOrderServiceConfig(serviceCode: "intl_express")
}
```

**Overenie:** `result` je `true` a pri odovzdaní v sklade `warehouses` obsahuje sklad, ktorý chcete použiť. `403` znamená, že služba nie je priradená tomuto zákazníkovi; `404` znamená, že kód služby neexistuje alebo je neaktívny.

## 7. Odhad ceny

Odhad ocení zásielku podľa cenového plánu služby bez toho, aby niečo zapísal. Celkovú sumu zobrazte nákupcovi a objednávku nevytvárajte, ak odhad hlási odmietnutie.

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

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipping-orders/services/intl_express/estimate-price \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "origin_type": "warehouse",
    "warehouse_id": 7,
    "delivery_postcode": "98104",
    "delivery_country": "US",
    "packages": [{
      "weight": 12,
      "length": 50,
      "width": 40,
      "height": 35,
      "weight_unit": 2,
      "dimension_unit": 2,
      "quantity": 2
    }]
  }'
```

```json
{
  "result": true,
  "data": {
    "needs_manual_quote": false,
    "shipping_fee": 118.40,
    "shipping_fee_total": 131.20,
    "fuel_surcharge": 12.80,
    "pickup_fee": null,
    "surcharges_total": 0,
    "sub_total": 131.20,
    "tax": { "total_tax": 17.06 },
    "total": 148.26,
    "currency": "CAD",
    "all_fees_calculated": true,
    "has_items_needing_quote": false,
    "unpriced_items": [],
    "refused": false,
    "refusal_message": null
  }
}
```

- `origin_type`: `warehouse` (zákazník odovzdá tovar v sklade; pošlite `warehouse_id`) alebo `pickup` (poskytovateľ tovar vyzdvihne; pošlite `pickup_postcode` a `pickup_country`). Použite iba hodnotu, ktorú povoľuje krok 5.
- `packages`: jeden riadok na skupinu rovnakých balíkov; `quantity` riadok násobí.
- `total` a `currency`: suma na zobrazenie. `total` je `null`, kým nie je vypočítaný niektorý poplatok.
- `needs_manual_quote` / `has_items_needing_quote`: firma oceňuje objednávku ručne; objednávku možno vytvoriť a zaplatí sa po tom, ako firma stanoví cenu.
- `refused` / `refusal_message`: služba odmieta zásielky, ktoré nedokáže oceniť. Objednávku nevytvárajte; namiesto toho zobrazte `refusal_message`.
- Voliteľné vstupy: `surcharges`, `products` (mapa id produktu na množstvo, zohľadňuje sa iba vtedy, keď je `allow_purchase_supplies` true), `has_special_requirements`, `coupon_code`.

**Overenie:** `result` je `true`, `refused` je `false` a buď `total` má hodnotu, alebo `needs_manual_quote` je `true`.

## 8. Vytvorenie prepravnej objednávky

Volanie vytvorenia rezervuje zásielku v službe. Integrácia uloží vrátené `id` k svojej nákupnej objednávke; toto id používa každé ďalšie volanie.

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

Pošlite `Idempotency-Key` odvodený od vášho vlastného stabilného id (tu od čísla nákupnej objednávky). Opakovanie s rovnakým kľúčom a rovnakým telom vráti prvú odpoveď s `"replayed": true` a hlavičkou `Idempotency-Replayed: true` a nevytvorí druhú objednávku.

```bash
curl -X POST https://YOUR_HOST/api/v1/customer/shipping-orders/services/intl_express/orders \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Idempotency-Key: hli-po-1058" \
  -d '{
    "origin_type": "warehouse",
    "warehouse_id": 7,
    "reference": "HLI-PO-1058",
    "delivery_name": "Cascade Tea Distributors",
    "delivery_telephone": "2065550143",
    "delivery_email": "receiving@cascadetea.example",
    "delivery_address_1": "300 5th Ave S",
    "delivery_city": "Seattle",
    "delivery_province": "WA",
    "delivery_country": "US",
    "delivery_postcode": "98104",
    "note": "Two cartons of sample tea, dock door B",
    "package": [{
      "description": "Sample tea carton",
      "weight": 12,
      "length": 50,
      "width": 40,
      "height": 35,
      "weight_unit": 2,
      "dimension_unit": 2,
      "value": 380,
      "quantity": 2
    }]
  }'
```

```json
{
  "result": true,
  "data": {
    "id": 9001,
    "status": 0,
    "total_price": 148.26,
    "price_breakdown": { "total": 148.26, "currency": "CAD" },
    "promotion_id": null,
    "tracking_number": null
  }
}
```

- Kľúč tela pre balíky je pri vytvorení `package` (pri odhade je to `packages`). Každý riadok s `quantity` N sa stane N balíkmi a každý balík dostane vlastné sledovacie číslo.
- Povinné polia: `delivery_name`, `delivery_telephone`, `delivery_address_1`, `delivery_city`, `delivery_province`, `delivery_country`, `delivery_postcode`, `origin_type`, `package[].weight`; plus `warehouse_id` pre `warehouse` alebo `pickup_name`, `pickup_telephone`, `pickup_address_1`, `pickup_city`, `pickup_province`, `pickup_country`, `pickup_postcode` pre `pickup`.
- Voliteľné polia: `reference` (ukladá sa ako `reference_number` objednávky), `delivery_email`, `delivery_address_2`, `scheduled_date`, `time_window`, `note`, `special_requirements` (pole textových riadkov, zohľadňuje sa iba vtedy, keď ich služba povoľuje), `products`, `surcharges`, `coupon_code`.
- `id`: uložte ho. `status` `0` znamená Čakajúca (čaká na platbu).
- `total_price`: suma, ktorú zaúčtuje krok 9. Je `0`, kým objednávka čaká na ručnú cenovú ponuku.
- `tracking_number` na úrovni objednávky je `null`; sledovacie čísla sú pri balíkoch a načítajú sa v kroku 10.
- Endpoint pri novej objednávke odpovie HTTP `201`.

**Overenie:** odpoveď obsahuje `result: true` a `id`. Zopakovanie tej istej požiadavky s rovnakým `Idempotency-Key` vráti rovnaké `id` s `"replayed": true`.

## 9. Platba objednávky zo zostatku účtu

Prepravné objednávky sa platia v plnej výške zo zostatku zákazníckeho účtu. Zaplatená objednávka prejde zo stavu Čakajúca do stavu Potvrdená a poskytovateľ ju začne vybavovať.

Najprv načítajte sumu:

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

```bash
curl https://YOUR_HOST/api/v1/customer/shipping-orders/9001/payment-info \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json"
```

```json
{
  "result": true,
  "data": {
    "order_id": 9001,
    "currency": "CAD",
    "total_price": "148.26",
    "paid_amount": "0.00",
    "remaining_balance": "148.26",
    "user_balance": "500.00",
    "has_sufficient_balance": true,
    "shortfall": 0,
    "payment_options": [
      { "type": "remaining_balance", "amount": 148.26 }
    ]
  }
}
```

Potom zaplaťte:

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

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

```json
{
  "result": true,
  "message": "Payment of $148.26 processed successfully.",
  "data": {
    "order_id": 9001,
    "status": 1,
    "amount_paid": "148.26"
  }
}
```

- `has_sufficient_balance` / `shortfall`: ak zostatok nepokrýva `remaining_balance`, pred platbou dobite účet.
- `payment_type`: podporuje sa iba `remaining_balance`; vždy sa zaúčtuje celá zostávajúca suma.
- `data.status` `1` znamená Potvrdená.

**Overenie:** volanie platby vráti `result: true` a `status` `1` a druhé volanie `payment-info` vráti `400`, pretože objednávka je úplne zaplatená. Volanie platby bez dostatočného zostatku vráti `422` a nič nezaúčtuje.

## 10. Načítanie objednávky a sledovanie balíkov

Volanie detailu vráti aktuálny stav a sledovacie číslo každého balíka. Sledovacie čísla balíkov uložte k nákupnej objednávke; verejné sledovanie prijíma každé z nich.

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

```bash
curl https://YOUR_HOST/api/v1/customer/shipping-orders/9001 \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json"
```

```json
{
  "result": true,
  "data": {
    "id": 9001,
    "status": 1,
    "status_name": "Confirmed",
    "can_edit": false,
    "can_cancel": true,
    "shipping_code": "K7RW2Q",
    "tracking_number": null,
    "reference_number": "HLI-PO-1058",
    "origin_type": "warehouse",
    "warehouse_id": 7,
    "delivery_address": { "name": "Cascade Tea Distributors", "city": "Seattle", "country": "US" },
    "package_count": 2,
    "packages": [
      { "id": 55101, "description": "Sample tea carton", "tracking_number": "SR123456789012", "weight": 12, "weight_unit": 2 },
      { "id": 55102, "description": "Sample tea carton", "tracking_number": "SR123456789013", "weight": 12, "weight_unit": 2 }
    ],
    "total_price": 148.26
  }
}
```

- `status`: `0` Čakajúca, `1` Potvrdená, `2` Na ceste, `3` Odoslaná, `4` Zrušená, `5` Neúspešná, `6` Čiastočne vyzdvihnutá, `7` Vyzdvihnutá, `8` Spracúva sa.
- `can_edit` / `can_cancel`: či je krok 12 momentálne povolený.
- `packages[].tracking_number`: čísla na uloženie a sledovanie.
- `shipping_code`: kód, ktorý prijímajú obrazovky odovzdania v sklade; vytlačte ho na sprievodné doklady k odovzdaniu.

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

```graphql
query CustomerShippingOrderShow {
  customerShippingOrderShow(id: 9001) {
    result
    message
    data {
      id
      status
      status_name
      can_cancel
      reference_number
      packages {
        id
        tracking_number
        weight
      }
      total_price
    }
  }
}
```

Na odsúhlasenie všetkých objednávok jednej služby, napríklad v nočnej úlohe, ich načítajte so zoznamom a filtrom. Filter `id` zodpovedá id objednávky, sledovaciemu číslu alebo referencii.

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

```bash
curl "https://YOUR_HOST/api/v1/customer/shipping-orders/services/intl_express/orders?id=HLI-PO-1058&created_at_from=2026-09-01&per_page=20" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json"
```

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

Verejné sledovanie nevyžaduje token a vracia časovú os udalostí jedného balíka:

**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 \
  -H "Accept: application/json"
```

```json
{
  "result": true,
  "deliveried": false,
  "data": [
    {
      "tracking_event_status_id": 1,
      "otep_status": "received",
      "description": "Received at warehouse",
      "location_city": "Toronto",
      "updated_at": "2026-09-29 10:42:00"
    }
  ]
}
```

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

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

**Overenie:** detail vráti objednávku tohto zákazníka s jedným sledovacím číslom na balík a verejné sledovanie vráti `result: true` pre sledovacie číslo balíka. Id objednávky iného zákazníka vráti `404`.

## 11. Prijímanie webhookov

Webhooky doručujú vytvorenie objednávky, zmeny stavu a udalosti sledovania na váš server, takže integrácia nemusí opakovane dopytovať. Zákaznícky účet si nastavuje vlastné URL webhookov a podpisové tajomstvo.

Pri vytvorení prepravnej objednávky poskytovateľ vytvorí aj prepojenú objednávku vyzdvihnutia pre svoj dispečing. Webhooky sa posielajú pre túto prepojenú objednávku: jej `ref` je `Shipping-Pickup-{shipping order id}` (napríklad `Shipping-Pickup-9001`) a každý jej balík nesie sledovacie číslo prepravného balíka v `external_tracking_number`. Prichádzajúce udalosti párujte podľa týchto dvoch polí.

| Nastavenie | Udalosť | Čo integrácia urobí |
|---|---|---|
| `order_create_webhook_url` | `order.created` | Prepojí udalosť s prepravnou objednávkou cez `ref` a `packages[].external_tracking_number` |
| `tracking_event_webhook_url` | `tracking.event` | Pridá udalosť do časovej osi balíka |
| `order_status_change_webhook_url` | `order.status_change` | Aktualizuje stav zobrazený vo vašom systéme |

**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" \
  -H "Accept: application/json" \
  -d '{
    "order_create_webhook_url": "https://erp.harbourline-imports.example/hooks/order-created",
    "tracking_event_webhook_url": "https://erp.harbourline-imports.example/hooks/tracking",
    "order_status_change_webhook_url": "https://erp.harbourline-imports.example/hooks/status",
    "webhook_sign_secret": "hli-webhook-secret-7f2c9a1e5b"
  }'
```

```json
{
  "result": true,
  "changed_keys": [
    "order_create_webhook_url",
    "tracking_event_webhook_url",
    "order_status_change_webhook_url",
    "webhook_sign_secret"
  ],
  "recipient_type": "customer",
  "settings": {
    "webhook_sign_secret": "*************************1e5b",
    "tracking_event_webhook_url": "https://erp.harbourline-imports.example/hooks/tracking"
  }
}
```

- Menia sa iba odoslané kľúče; prázdny reťazec URL vymaže. `webhook_sign_secret` musí mať 16 až 255 znakov a kým je tajomstvo prázdne, neodošle sa žiadny webhook.
- `recipient_type` je pri zákazníckom účte `customer`.

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

```graphql
mutation WebhookSettingsUpdate {
  webhookSettingsUpdate(
    order_status_change_webhook_url: "https://erp.harbourline-imports.example/hooks/status"
    tracking_event_webhook_url: "https://erp.harbourline-imports.example/hooks/tracking"
  )
}
```

Overte podpis **v2** nad surovým telom: `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** 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:** po volaní nastavení jedno testovacie vytvorenie vyvolá udalosť `order.created`, ktorej `ref` je `Shipping-Pickup-{id}` pre id novej prepravnej objednávky, a kontrola podpisu prejde.

## 12. Úprava alebo zrušenie objednávky

Objednávku možno opraviť, kým je v stave Čakajúca (pred platbou), a zrušiť, kým je v stave Čakajúca alebo Potvrdená. Zrušenie zaplatenej objednávky vráti zaplatenú sumu do kreditu účtu.

Na úpravu pošlite znova celú objednávku s rovnakými poľami ako v kroku 8. Cena sa prepočíta.

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

```bash
curl -X PUT https://YOUR_HOST/api/v1/customer/shipping-orders/9001 \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "origin_type": "warehouse",
    "warehouse_id": 7,
    "reference": "HLI-PO-1058",
    "delivery_name": "Cascade Tea Distributors",
    "delivery_telephone": "2065550143",
    "delivery_email": "receiving@cascadetea.example",
    "delivery_address_1": "300 5th Ave S",
    "delivery_address_2": "Suite 210",
    "delivery_city": "Seattle",
    "delivery_province": "WA",
    "delivery_country": "US",
    "delivery_postcode": "98104",
    "package": [{
      "description": "Sample tea carton",
      "weight": 12,
      "length": 50,
      "width": 40,
      "height": 35,
      "weight_unit": 2,
      "dimension_unit": 2,
      "value": 380,
      "quantity": 2
    }]
  }'
```

```json
{
  "result": true,
  "data": {
    "id": 9001,
    "status": 0,
    "total_price": 148.26,
    "promotion_id": null,
    "coupon_code": null,
    "promotion_discount": null
  }
}
```

Na zrušenie:

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

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

```json
{
  "result": true,
  "data": {
    "id": 9001,
    "status": 4,
    "refund_amount": 148.26
  },
  "message": "Order cancelled. $148.26 refunded to your credit."
}
```

- `status` `4` znamená Zrušená. Prepojená objednávka vyzdvihnutia sa odstráni.
- `refund_amount`: suma vrátená do kreditu účtu; `0` pri nezaplatenej objednávke.
- Pred ponúknutím týchto akcií používateľom načítajte `can_edit` a `can_cancel` z kroku 10.

**Overenie:** zrušenie vráti `status` `4` a detail ukazuje `status_name` `Cancelled`. Druhé zrušenie alebo zrušenie objednávky v stave Na ceste alebo neskoršom vráti `403` so správou `This order can no longer be cancelled.`; úprava zaplatenej objednávky vráti `403`.

## 13. Spracovanie chýb

| Situácia | Stav HTTP | Kód | Čo integrácia urobí |
|---|---|---|---|
| Chýbajúci, expirovaný alebo neplatný token; prihlásenie nezákazníckym účtom alebo bez oprávnenia na API | `401` | — | Prihláste sa znova; ak zlyhá samotné prihlásenie, požiadajte firmu o kontrolu typu účtu a oprávnenia na API |
| Služba nie je priradená tomuto zákazníkovi | `403` | — | Znova načítajte zoznam služieb (krok 5) a rezervujte iba priradené služby |
| API sa volá z relácie aplikácie platformy, ktorej aplikácia má vypnuté prepravné objednávky | `403` | `APP_CAPABILITY_DISABLED` | Požiadajte firmu o zapnutie prepravných objednávok pre aplikáciu |
| Neznámy alebo neaktívny kód služby; id objednávky sa pre tohto zákazníka nenašlo | `404` | — | Obnovte zoznam služieb; skontrolujte uložené id objednávky |
| Povinné pole chýba alebo je neplatné | `422` | — | Prečítajte `errors` v tele, opravte polia a odošlite znova |
| Služba neponúka daný typ pôvodu alebo sklad nie je v zozname služby | `422` | — | Použite `origin_type` a `warehouse_id` z krokov 5 a 6 |
| Služba nedokáže zásielku oceniť a neocenené zásielky odmieta | `422` | `unpriced_refused` | Nič sa nevytvorilo; zobrazte `message` a neopakujte bez zmeny |
| Objednaný spotrebný materiál nie je na sklade | `422` | — | Prečítajte `stock_shortages`, znížte množstvá a odošlite znova |
| Rovnaký `Idempotency-Key` s iným telom | `409` | `IDEMPOTENCY_CONFLICT` | Pre novú objednávku použite nový kľúč; kľúč nikdy nepoužívajte znova pre iný obsah |
| Opakovanie, kým prvá požiadavka s týmto kľúčom ešte prebieha | `409` | `IDEMPOTENCY_IN_PROGRESS` | Počkajte počet sekúnd z `Retry-After` a potom zopakujte s rovnakým kľúčom a telom |
| Platba bez dostatočného zostatku | `422` | — | Dobite účet a potom zaplaťte znova |
| Informácie o platbe alebo platba pri úplne zaplatenej objednávke | `400` | — | Objednávku považujte za zaplatenú; načítajte detail |
| Zrušenie po tom, ako objednávka opustila stav Čakajúca alebo Potvrdená | `403` | — | Zobrazte, že objednávku už nemožno zrušiť; kontaktujte firmu |
| Úprava po platbe | `403` | — | Zrušte ju a vytvorte novú objednávku, alebo kontaktujte firmu |
| Chyba servera počas odhadu, vytvorenia, platby alebo zrušenia | `500` | — | Zopakujte raz; pri vytvorení zopakujte s rovnakým `Idempotency-Key` |

## Zoznam testov

Použite testovaciu referenciu, napríklad `DEV-SHIP-001` alebo `HLI-PO-1058`:

- [ ] Prihlásenie zákazníka vráti `access_token`; požiadavka bez tokenu vráti `401`.
- [ ] Zoznam služieb nie je prázdny a uložili ste jeden `service_code`.
- [ ] Konfigurácia vráti sklady, jednotky a povolené krajiny pre túto službu a váš formulár ich používa.
- [ ] Odhad vráti `total` (alebo `needs_manual_quote: true`) a odmietnutá zásielka sa nevytvorí.
- [ ] Vytvorenie vráti `id`; rovnaký `Idempotency-Key` s rovnakým telom vráti rovnaké `id` s `"replayed": true`.
- [ ] Platba uspeje a stav sa zmení na Potvrdená, alebo ste overili, že nedostatočný zostatok vráti `422` a nič nezaúčtuje.
- [ ] Detail ukazuje objednávku tohto zákazníka s jedným sledovacím číslom na balík a verejné sledovanie nájde každý balík.
- [ ] Webhooky sú nastavené s podpisovým tajomstvom; testovacie vytvorenie vyvolá `order.created` s `ref` `Shipping-Pickup-{id}` a kontrola podpisu prejde.
- [ ] Zrušenie testovacej objednávky vráti `status` `4` a očakávanú `refund_amount`; druhé zrušenie vráti `403`.
