# Etykiety przewoźnika

Zakup przesyłki przewoźnika: lista metod → wycena → utworzenie etykiety → pobranie PDF → śledzenie → odbiór zdarzeń → anulowanie (lub zamknięcie dnia).

Ten przewodnik dokumentuje wyłącznie poniższe operacje. Wykonaj je w kolejności.

Zamień `YOUR_HOST`, `ACCESS_TOKEN` i `shipping_method` na swoje. Identyfikatory metod różnią się per konto — nigdy ich nie hardkoduj.

## 1. Uwierzytelnienie

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

```
Authorization: Bearer ACCESS_TOKEN
```

GraphQL używa tego samego nagłówka na `POST /api/graphql`.

[Podręcznik REST](/api/documentation#/paths/v1-user-login/post) · [Podręcznik GraphQL](/api/graphql/documentation#/user/userLogin)

**Weryfikacja:** logowanie zwraca `access_token`. Późniejsze wywołanie bez niego zwraca `401`.

## 2. Lista metod wysyłki

**REST:** `POST /api/v1/labelservice/getShippingMethodList` — [Podręcznik REST](/api/documentation#/paths/v1-labelservice-getShippingMethodList/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/getShippingMethodList \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"detail": true}'
```

Każdy wiersz ma:

| Pole | Użycie |
|---|---|
| `id` | `shipping_method` w każdym późniejszym wywołaniu |
| `name` | Nazwa wyświetlana |
| `unique_identifier` | Stabilny kod |
| `options.signature_option` | Podpis dostępny |
| `options.insurance_option` | Ubezpieczenie dostępne |
| `options.multi_package` | Więcej niż jedna sztuka |

**GraphQL:** `labelserviceGetShippingMethodList` ([Podręcznik GraphQL](/api/graphql/documentation#/labelservice/labelserviceGetShippingMethodList)) (skalar JSON).

**Weryfikacja:** lista nie jest pusta. Wybrałeś jedno `id` i wiesz, czy ta metoda pozwala na podpis, ubezpieczenie i wiele paczek. Pusta lista oznacza, że na koncie nie włączono żadnej metody.

## 3. Wycena

Przebieg próbny. Przewoźnik jest pytany o cenę; nic nie jest rezerwowane. Ciało ma ten sam kształt co przy tworzeniu. `shipping_method` jest wymagane.

**REST:** `POST /api/v1/labelservice/rate` — [Podręcznik REST](/api/documentation#/paths/v1-labelservice-rate/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/rate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "shipping_method": 59,
    "name": "Jane Recipient",
    "telephone": "5555555555",
    "email": "jane@example.com",
    "address_1": "6701 RUE HADLEY",
    "city": "Montreal",
    "province": "QC",
    "postcode": "H4E3R3",
    "country": "CA",
    "weight": 1,
    "length": 30,
    "width": 20,
    "height": 10,
    "dimension_unit": 2,
    "weight_unit": 2,
    "signature_option": 0,
    "insurance_option": 0,
    "package_type": "parcel",
    "ref": "DEV-LABEL-001",
    "sender_name": "Acme Warehouse",
    "sender_telephone": "5555555555",
    "sender_address_1": "2667 boul des sources",
    "sender_city": "Pointe-Claire",
    "sender_province": "QC",
    "sender_postcode": "H9S2H9",
    "sender_country": "CA"
  }'
```

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

Dla więcej niż jednej sztuki wyślij `packages: [{ ref, weight, length, width, height, weight_unit, dimension_unit }]`. `shipping_from` (id książki adresowej) lub `shipping_from_code` może zastąpić blok `sender_*`.

**GraphQL:** `labelserviceRate` ([Podręcznik GraphQL](/api/graphql/documentation#/labelservice/labelserviceRate)).

**Weryfikacja:** `result` jest true i masz cenę (oraz dni tranzytu, gdy przewoźnik je wysyła). Jeśli nie ma stawki, popraw cel / paczkę / metodę **przed** utworzeniem.

## 4. Utworzenie etykiety

To rezerwuje przesyłkę u przewoźnika.

**REST:** `POST /api/v1/labelservice/submitOrder` — [Podręcznik REST](/api/documentation#/paths/v1-labelservice-submitOrder/post)

To samo ciało co w kroku 3. Wyślij `Idempotency-Key`.

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/submitOrder \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: dev-label-001" \
  -d '{
    "shipping_method": 59,
    "name": "Jane Recipient",
    "telephone": "5555555555",
    "email": "jane@example.com",
    "address_1": "6701 RUE HADLEY",
    "city": "Montreal",
    "province": "QC",
    "postcode": "H4E3R3",
    "country": "CA",
    "weight": 1,
    "length": 30,
    "width": 20,
    "height": 10,
    "dimension_unit": 2,
    "weight_unit": 2,
    "signature_option": 0,
    "insurance_option": 0,
    "package_type": "parcel",
    "ref": "DEV-LABEL-001",
    "sender_name": "Acme Warehouse",
    "sender_telephone": "5555555555",
    "sender_address_1": "2667 boul des sources",
    "sender_city": "Pointe-Claire",
    "sender_province": "QC",
    "sender_postcode": "H9S2H9",
    "sender_country": "CA"
  }'
```

**GraphQL:** `labelserviceSubmitOrder` ([Podręcznik GraphQL](/api/graphql/documentation#/labelservice/labelserviceSubmitOrder)).

```json
{
  "result": true,
  "id": 12345,
  "shipping_price": "12.50",
  "tracking_numbers": ["1Z999AA10123456784"],
  "external_id": "EXT_12345"
}
```

| Pole | Użycie |
|---|---|
| `id` | Id zamówienia Superroute — pobranie i anulowanie |
| `tracking_numbers` | Daj je kupującemu; śledzenie je przyjmuje |
| `external_id` | Id przesyłki przewoźnika |
| `shipping_price` | Kwota naliczona |

**Weryfikacja:** `tracking_numbers` nie jest puste. Zapisz `id` i numery. Ten sam `Idempotency-Key` nie może kupić drugiej etykiety.

## 5. Pobranie PDF

**REST:** `POST /api/v1/labelservice/getShippingLabel` — [Podręcznik REST](/api/documentation#/paths/v1-labelservice-getShippingLabel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/getShippingLabel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "12345",
    "type": "ORDER_ID",
    "base64": 1
  }'
```

`type` może być `ORDER_ID`, `TRACKING_NUMBER` lub `REF`. `base64: 0` przesyła PDF. To **oficjalna etykieta przewoźnika**. Liczba sztuk jest ustalona przez rezerwację.

**GraphQL:** `labelserviceGetShippingLabel` ([Podręcznik GraphQL](/api/graphql/documentation#/labelservice/labelserviceGetShippingLabel)).

**Weryfikacja:** PDF się otwiera i pokazuje kod kreskowy / numer śledzenia przewoźnika z kroku 4. Wydrukuj jedną kopię testową i wyrzuć — nie dawaj testowej etykiety przewoźnikowi.

## 6. Śledzenie

Bez tokenu. Użyj numeru z `tracking_numbers`:

```bash
curl https://YOUR_HOST/api/v1/tracking/1Z999AA10123456784
```

[Podręcznik REST](/api/documentation#/operations/getPublicTracking) · [Podręcznik GraphQL](/api/graphql/documentation#/tracking/trackingPublic)

**GraphQL:**

```graphql
query {
  trackingPublic(trackingNumber: "1Z999AA10123456784") {
    result
    deliveried
    is_third_party_tracking
    data {
      tracking_event_status_id
      tracking_event_key
      description
      updated_at_localized
    }
    proofs { file_id type full_url signed_url }
  }
}
```

`is_third_party_tracking` jest true, gdy zdarzenia pochodzą od przewoźnika. Rozgałęziaj po `tracking_event_status_id` / `tracking_event_key`, nie po `description`. Najnowsze zdarzenie jest pierwsze w `data`. Wczesne zdarzenia mogą nadal być «informacje przekazane», aż przewoźnik zeskanuje paczkę. `500` to dostarczone; `proofs[]` może wtedy zawierać podpis (`type` `1`) lub zdjęcie (`type` `2`).

**Weryfikacja:** wyszukiwanie zwraca przesyłkę, którą właśnie utworzyłeś. Nieznany numer to `result: false` / 404.

## 7. Konfiguracja powiadomień o zdarzeniach

| Ustawienie | Zdarzenie | Kiedy |
|---|---|---|
| `order_create_webhook_url` | `order.created` | Zapisać `id` i numery śledzenia przewoźnika |
| `tracking_event_webhook_url` | `tracking.event` | Skanowania przewoźnika, w drodze, dostarczone |
| `order_status_change_webhook_url` | `order.status_change` | Status w Twoim systemie |
| `order_cancel_failed_webhook_url` | `order.cancel_failed` | Anulowanie odrzucone, bo przewoźnik już ma paczkę |

**REST:** `PUT /api/v1/webhook-settings` — [Podręcznik REST](/api/documentation#/paths/v1-webhook-settings/put)

**GraphQL:** `webhookSettingsUpdate` ([Podręcznik 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",
    "tracking_event_webhook_url": "https://erp.example.com/hooks/superroute",
    "webhook_sign_secret": "YOUR_LONG_RANDOM_SECRET",
    "webhook_verify_ssl": 1
  }'
```

Weryfikuj **v2** na surowym ciele: `HMAC_SHA256(timestamp + "." + raw_body, secret)` wobec `X-Webhook-Signature-V2`. Deduplikuj po `X-Webhook-Event-Id`. Odpowiedz **2xx w mniej niż 3 sekundy**.

```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;
}
```

**Weryfikacja:** jeden testowy `submitOrder` produkuje `order.created` z tymi `tracking_numbers`. Zły sekret to `401` od Twojego odbiorcy.

## 8. Anulowanie

Tylko dopóki przewoźnik jeszcze na to pozwala (zwykle przed odbiorem).

**REST:** `POST /api/v1/labelservice/cancelShippingLabel` — [Podręcznik REST](/api/documentation#/paths/v1-labelservice-cancelShippingLabel/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/cancelShippingLabel \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":"12345","type":"ORDER_ID"}'
```

**GraphQL:** `labelserviceCancelShippingLabel` ([Podręcznik GraphQL](/api/graphql/documentation#/labelservice/labelserviceCancelShippingLabel)).

Przewoźnik, który już ma paczkę, odmówi — to jest `order.cancel_failed`.

**Weryfikacja:** drugie anulowanie jest bezpieczne. Śledzenie nie traktuje już przesyłki jako aktywnej.

## 9. Koniec dnia (tylko jeśli ta metoda tego wymaga)

Niektórzy przewoźnicy potrzebują dziennego manifestu.

**REST:** `POST /api/v1/labelservice/endofday` — [Podręcznik REST](/api/documentation#/paths/v1-labelservice-endofday/post)

```bash
curl -X POST https://YOUR_HOST/api/v1/labelservice/endofday \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"shipping_method": 59}'
```

**GraphQL:** `labelserviceEndofday` ([Podręcznik GraphQL](/api/graphql/documentation#/labelservice/labelserviceEndofday)).

Wywołaj raz po ostatniej etykiecie dnia wysyłki. Pomiń ten krok, gdy metoda z kroku 2 nie ma takiego wymagania.

**Weryfikacja:** ciało sukcesu (lub potwierdzenie przewoźnika) wymienia dzisiejsze etykiety. Uruchom najpierw na metodzie testowej.

## Lista testów

Użyj celu, który kontrolujesz, i metody, którą można anulować:

- [ ] Lista metod nie jest pusta; zapisałeś jedno `id`.
- [ ] Stawka zwraca cenę dla tej metody i celu.
- [ ] Submit zwraca `tracking_numbers`; ten sam `Idempotency-Key` nie kupuje drugiej etykiety.
- [ ] PDF etykiety otwiera się i pokazuje numer śledzenia przewoźnika.
- [ ] Publiczne śledzenie znajduje przesyłkę po tym numerze.
- [ ] Przychodzi `order.created`; podpis v2 weryfikuje się.
- [ ] Anulowanie działa, **albo** potwierdziłeś, że tej metody nie da się anulować po rezerwacji.
- [ ] Jeśli metoda wymaga końca dnia, testowy przebieg kończy się bez błędu.
