# Vyzdvihnutie a doručenie

Vyzdvihnutie a posledná míľa vlastnou flotilou používajú rovnaké API. Vytvorenie objednávky vyzdvihnutia alebo doručenia → tlač miestneho štítku → sledovanie → odber oznámení o udalostiach → zrušenie testovacej objednávky.

`type` vyberá zastávku: `D` doručenie, `P` vyzdvihnutie. Vykonajte kroky v poradí. Cenová ponuka je voliteľná a pred vytvorením objednávky nie je nutná.

Nahraďte `YOUR_HOST` a `ACCESS_TOKEN` hodnotami z vášho prostredia.

## 1. Prihlásenie

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

Vrátený `access_token` vložte do hlavičky požiadavky:

```
Authorization: Bearer ACCESS_TOKEN
```

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

[Príručka REST](/api/documentation#/paths/v1-user-login/post) · [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`.

## 2. Cenová ponuka (voliteľne)

Tento krok je voliteľný. Vracia iba cenovú ponuku; objednávka sa nevytvára. Vytvorenie nevyžaduje predchádzajúcu ponuku. Nastavte `type` na `D` (doručenie) alebo `P` (vyzdvihnutie).

**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": "H9S2H9",
    "from_country": "CA",
    "to_postcode": "H4B2T5",
    "to_country": "CA",
    "packages": [{
      "weight": 1,
      "weight_unit": 2,
      "length": 30,
      "width": 20,
      "height": 10,
      "dimension_unit": 2
    }]
  }'
```

`weight_unit`: `1` g, `2` kg, `3` oz, `4` lb. `dimension_unit`: `1` mm, `2` cm, `3` m, `4` in.

```json
{
  "result": true,
  "shipping_price": "6.99",
  "currency": "CAD",
  "price_details": {
    "shipping_fee": "6.99",
    "tax_details": [{ "tax_name": "HST", "tax_rate": "13.00", "tax": "0.91" }]
  }
}
```

**GraphQL** (JSON skalár — bez selection set) (`ordersRate` ([Príručka GraphQL](/api/graphql/documentation#/orders/ordersRate))):

```graphql
mutation {
  ordersRate(
    type: "D"
    from_postcode: "H9S2H9"
    from_country: "CA"
    to_postcode: "H4B2T5"
    to_country: "CA"
    packages: [{ weight: 1, weight_unit: 2, length: 30, width: 20, height: 10, dimension_unit: 2 }]
  )
}
```

**Overenie:** ak toto volanie spustíte, `result` je true a `shipping_price` je číslo. Zopakujte s `type` `P` pre ponuku vyzdvihnutia. Prázdna cena znamená, že PSČ nie je v aktívnej oblasti. Vytvorenie na tomto kroku nezávisí.

## 3. Vytvorenie objednávky

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

Požiadavka musí obsahovať `Idempotency-Key`, aby opakovanie nemohlo vytvoriť druhú objednávku.

### Doručenie (`type` `D`)

```bash
curl -X POST https://YOUR_HOST/api/v1/client/orderCreate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-local-001" \
  -d '{
    "type": "D",
    "need_pick_up": 0,
    "ref": "DEV-LOCAL-001",
    "name": "Jane Recipient",
    "telephone": "5555555555",
    "email": "jane@example.com",
    "address_1": "2667 boul des sources",
    "city": "Pointe-Claire",
    "province": "QC",
    "postcode": "H9S2H9",
    "country": "Canada",
    "packages": 1,
    "packagesDetail": [{
      "ref": "PKG-001",
      "weight": 1,
      "weight_unit": 2,
      "length": 30,
      "width": 20,
      "height": 10,
      "dimension_unit": 2
    }],
    "delivery_instruction": "Leave at the side door"
  }'
```

### Vyzdvihnutie (`type` `P`)

Rovnaký endpoint. Adresa je zastávka vyzdvihnutia.

```bash
curl -X POST https://YOUR_HOST/api/v1/client/orderCreate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-pickup-001" \
  -d '{
    "type": "P",
    "need_pick_up": 1,
    "ref": "DEV-PICKUP-001",
    "name": "Jane Sender",
    "telephone": "5555555555",
    "email": "jane@example.com",
    "address_1": "2667 boul des sources",
    "city": "Pointe-Claire",
    "province": "QC",
    "postcode": "H9S2H9",
    "country": "Canada",
    "packages": 1,
    "packagesDetail": [{
      "ref": "PKG-P-001",
      "weight": 1,
      "weight_unit": 2,
      "length": 30,
      "width": 20,
      "height": 10,
      "dimension_unit": 2
    }],
    "pickup_instruction": "Ring the side bell"
  }'
```

| Pole | Význam |
|---|---|
| `type` | `D` doručenie, alebo `P` vyzdvihnutie |
| `need_pick_up` | `0` — zásielka už je v sklade. `1` — vodič musí zásielku vyzdvihnúť |
| `ref` | Externá referencia na vyhľadanie a odsúhlasenie |
| `name` / adresa | Doručenie: príjemca. Vyzdvihnutie: zastávka vyzdvihnutia |
| `auto_deduplication` | `1` odmietne druhú zásielku s rovnakou `ref` zásielky |

**GraphQL:** `clientOrderCreate` ([Príručka GraphQL](/api/graphql/documentation#/client/clientOrderCreate)) (JSON skalár).

```json
{ "result": true, "id": 12345, "tracking_number": "SR123456789012", "orders_status_id": 2 }
```

`orders_status_id` `2` je Nová. Uložte `id`, `tracking_number` a `ref`.

**Overenie:** pošlite **rovnaké** telo s rovnakým `Idempotency-Key` znova. Musíte dostať rovnaké `id` a nesmiete vytvoriť druhú objednávku.

Objednávka sa **nevytvorí**, keď:

| `code` | Postup |
|---|---|
| `INSUFFICIENT_BALANCE` | `insufficient_balance` obsahuje required / available / shortfall. Dobite, potom skúste znova. |
| `OUT_OF_DELIVERY_AREA` | Adresa je mimo obsluhovanú oblasť a firma také objednávky maže. Odošlite adresu v obsluhovanej oblasti. |
| `IDEMPOTENCY_CONFLICT` | Rovnaký idempotenčný kľúč bol znova použitý s iným telom požiadavky. Použite nový kľúč. |

Zachovaná objednávka mimo oblasti môže predsa vrátiť `result: true` s `shipping_price: null` a `warning`. Prečítajte to pole.

## 4. Načítanie objednávky

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

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

**REST:** `GET /api/v1/orders/list` — všetky objednávky účtu, najnovšie ako prvé, každá so svojimi balíkmi a ich položkami. Poslaním `page` a `per_page` spolu stránkujete (`per_page` najviac 1000); bez nich dostanete najnovších 1000 objednávok a príznak `truncated`. [Príručka REST](/api/documentation#/paths/v1-orders-list/get)

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

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

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

**Overenie:** objednávka patrí k prihlásenému účtu. `ref` zodpovedá hodnote, ktorú ste vytvorili. `tracking_number` zodpovedá kroku 3.

## 5. Tlač miestneho štítku

**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
  }'
```

`type`: `TRACKING_NUMBER`, `ORDER_ID` alebo `REF`. `base64: 0` (predvolené) streamuje PDF. Pri `base64: 1` je celé telo odpovede reťazec JSON na najvyššej úrovni obsahujúci PDF v base64, nie objekt s poľom `pdf_data`. Ak chcete štítok v bežnom objekte JSON, zavolajte radšej `POST /api/v2/shipping/getShippingLabel`.

**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:** PDF sa otvorí. Štítok doručenia ukazuje príjemcu; štítok vyzdvihnutia ukazuje adresu vyzdvihnutia. Skryté adresné pole je na štítku prázdne.

## 6. Sledovanie

Verejný endpoint sledovania; prístupový token nie je potrebný.

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

[Príručka REST](/api/documentation#/operations/getPublicTracking) · [Príručka GraphQL](/api/graphql/documentation#/tracking/trackingPublic)

Rovnaká URL prijíma vaše `ref`, keď bolo uložené ako externé číslo.

**GraphQL** (typované — potrebuje 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 }
  }
}
```

Vetvenie podľa `tracking_event_status_id`, nie podľa `description` (tento reťazec sleduje `Accept-Language`):

| `tracking_event_status_id` | Strana | Význam |
|---|---|---|
| `100` | obe | Objednávka prijatá |
| `300` / `301` | doručenie | V zariadení |
| `450` | doručenie | Na ceste k doručeniu |
| `500` | doručenie | Doručené |
| `501` | doručenie | Doručenie zlyhalo, je potrebný nový plán |
| `460` | vyzdvihnutie | Na ceste k vyzdvihnutiu |
| `510` | vyzdvihnutie | Vyzdvihnuté |
| `512` | vyzdvihnutie | Vyzdvihnutie zlyhalo, skúste neskôr |
| `513` | vyzdvihnutie | Problém pri vyzdvihnutí |

`data` je od najnovšej. Prvý riadok berte ako aktuálny. `deliveried: true` po `500`.

Pri `500` alebo `510` môže `proofs[]` obsahovať `type` `1` (podpis) alebo `2` (fotka) plus `file_id` a `signed_url`. Fotka nahratá po tejto udalosti v tomto payloade nie je — v kroku 7 odoberajte `pod.files_updated`.

**Overenie:** hneď po vytvorení je najnovšia udalosť `100` a `deliveried` je false. Neznáme číslo je `result: false` / 404 — zobrazte stav nenájdené; nevymýšľajte sledovacie udalosti.

## 7. Konfigurácia oznámení o udalostiach

Nakonfigurujte URL spätných volaní, ktoré tento tok vyžaduje:

| Nastavenie | Udalosť | Kedy |
|---|---|---|
| `order_create_webhook_url` | `order.created` | Uložiť `id` a `tracking_number` |
| `order_status_change_webhook_url` | `order.status_change` | Stav zobrazený zákazníkovi |
| `tracking_event_webhook_url` | `tracking.event` | Časová os vyzdvihnutia alebo doručenia |
| `pod_files_webhook_url` | `pod.files_updated` | Fotka/podpis po vyzdvihnutí alebo doručení |
| `order_cancel_failed_webhook_url` | `order.cancel_failed` | Zrušenie, ktoré ste odoslali, bolo odmietnuté |

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

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

```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.example.com/hooks/superroute",
    "order_status_change_webhook_url": "https://erp.example.com/hooks/superroute",
    "tracking_event_webhook_url": "https://erp.example.com/hooks/superroute",
    "webhook_sign_secret": "YOUR_LONG_RANDOM_SECRET",
    "webhook_verify_ssl": 1
  }'
```

Overujte **v2** nad surovým telom: `HMAC_SHA256(timestamp + "." + raw_body, secret)` proti `X-Webhook-Signature-V2`. Deduplikujte 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:** vytvorte jednu testovaciu objednávku a uvidíte `order.created` s rovnakým `id` / `tracking_number`. Neplatný podpis musí prijímač odmietnuť kódom `401`. Druhé doručenie rovnakého `X-Webhook-Event-Id` sa nesmie spracovať dvakrát.

## 8. Zrušenie testovacej objednávky

**REST:** `POST /api/v1/orders/cancel` — [Príručka REST](/api/documentation#/paths/v1-orders-cancel/post) — 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"}'
```

Už zrušené: `200` s `already_cancelled: true`. **GraphQL:** `ordersCancel` ([Príručka GraphQL](/api/graphql/documentation#/orders/ordersCancel)).

**Overenie:** verejné sledovanie zásielku už neberie ako aktívnu. Ak je zrušenie odmietnuté (`409`, napr. `ORDER_ALREADY_IN_DELIVERY`), spustí sa `order.cancel_failed`.

## 9. Dávkové vytvorenie (voliteľne)

- `POST /api/v1/client/batchOrderCreate` — [Príručka REST](/api/documentation#/paths/v1-client-batchOrderCreate/post) — čaká, kým nie je spracovaný každý riadok.
- `POST /api/v1/client/batchOrderCreateAsync` — [Príručka REST](/api/documentation#/paths/v1-client-batchOrderCreateAsync/post) — hneď vráti identifikátor úlohy; pollujte `GET /api/v1/client/async/{id}` — [Príručka REST](/api/documentation#/paths/v1-client-async-id/get) alebo berte `order.create_async`.

Rovnaké polia ako krok 3, ako pole objednávok. Každý riadok môže byť `type` `D` alebo `P`. **GraphQL:** `clientBatchOrderCreate` ([Príručka GraphQL](/api/graphql/documentation#/client/clientBatchOrderCreate)) / `clientBatchOrderCreateAsync` ([Príručka GraphQL](/api/graphql/documentation#/client/clientBatchOrderCreateAsync)).

**Overenie:** každý riadok má vlastné `result`. Asynchrónny endpoint použite, keď odosielate viac ako približne 100 riadkov.

## Zoznam testov

Použite testovacie `ref` ako `DEV-LOCAL-001` / `DEV-PICKUP-001`:

- [ ] (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`; `need_pick_up` je `1`.
- [ ] Zoznam / detail ukáže objednávku pod týmto účtom.
- [ ] PDF miestneho štítku sa otvorí a ukáže príjemcu alebo adresu vyzdvihnutia.
- [ ] Verejné sledovanie vráti časovú os bez tokenu; najnovšia udalosť je `100`.
- [ ] Príde `order.created`; podpis v2 overí.
- [ ] Zrušenie vráti `result: true` (alebo `already_cancelled: true` pri opakovaní).
