# Usługi przewozowe

Użyj własnych usług przewozowych firmy logistycznej: lista usług → wczytanie konfiguracji usługi → wycena → utworzenie zamówienia → płatność → odczyt zwrotny → śledzenie → anulowanie zamówienia testowego.

Ten przewodnik dokumentuje wyłącznie poniższe operacje. Wykonaj je w kolejności. Uwierzytelnianie używa konta **klienta**.

Zamień `YOUR_HOST` i `ACCESS_TOKEN` na wartości ze swojego środowiska.

## 1. Uwierzytelnienie (klient)

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

Użyj `access_token` tak:

```
Authorization: Bearer ACCESS_TOKEN
```

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

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

## 2. Lista usług

**REST:** `GET /api/v1/customer/shipping-orders/services` — [Podręcznik 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"
```

**GraphQL:** `customerShippingOrderServices` ([Podręcznik GraphQL](/api/graphql/documentation#/customer/customerShippingOrderServices))

Każdy wiersz ma `service_code`, `name`, `offer_pickup`, `allow_warehouse_delivery`. Pusta lista oznacza, że temu klientowi nie przypisano żadnej usługi.

**Weryfikacja:** zapisałeś jeden `service_code` (przykład poniżej: `intl_express`).

## 3. Wczytanie konfiguracji tej usługi

**REST:** `GET /api/v1/customer/shipping-orders/services/{serviceCode}/config` — [Podręcznik 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"
```

**GraphQL:** `customerShippingOrderServiceConfig` ([Podręcznik GraphQL](/api/graphql/documentation#/customer/customerShippingOrderServiceConfig))

**Weryfikacja:** masz co najmniej jedno `id` magazynu, jeśli ta usługa pozwala na nadanie w magazynie. `403` oznacza, że ten klient nie ma dostępu do tej usługi.

## 4. Wycena

**REST:** `POST /api/v1/customer/shipping-orders/services/{serviceCode}/estimate-price`

```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" \
  -d '{
    "origin_type": "warehouse",
    "warehouse_id": 7,
    "delivery_postcode": "M5V2H1",
    "delivery_country": "CA",
    "packages": [{
      "weight": 2,
      "length": 30,
      "width": 20,
      "height": 15,
      "weight_unit": 2,
      "dimension_unit": 2
    }]
  }'
```

`origin_type`: `warehouse` (nadanie w magazynie) lub `pickup` (firma odbiera). Dopasuj do tego, co krok 3 podał jako dozwolone dla usługi.

**Weryfikacja:** `result` jest true i masz cenę (albo flagę „wymaga wyceny” przy ręcznym cenniku). Jeszcze nie twórz, jeśli cel/paczka jest odrzucona.

## 5. Utworzenie zamówienia

**REST:** `POST /api/v1/customer/shipping-orders/services/{serviceCode}/orders`

Wyślij `Idempotency-Key`.

```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 "Idempotency-Key: dev-ship-001" \
  -d '{
    "origin_type": "warehouse",
    "warehouse_id": 7,
    "reference": "DEV-SHIP-001",
    "delivery_name": "Jane Recipient",
    "delivery_telephone": "5555555555",
    "delivery_email": "jane@example.com",
    "delivery_address_1": "123 King St W",
    "delivery_city": "Toronto",
    "delivery_province": "ON",
    "delivery_country": "CA",
    "delivery_postcode": "M5V2H1",
    "package": [{
      "weight": 2,
      "length": 30,
      "width": 20,
      "height": 15,
      "weight_unit": 2,
      "dimension_unit": 2,
      "quantity": 1
    }]
  }'
```

**Weryfikacja:** odpowiedź ma `id` zamówienia. Zapisz `id`, `tracking_number` / `reference_number`, gdy są obecne.

## 6. Płatność

**REST:** `POST /api/v1/customer/shipping-orders/{id}/pay`

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

Opcjonalnie: najpierw `GET /api/v1/customer/shipping-orders/{id}/payment-info`. `402` oznacza, że portfel nie pokrywa kwoty — doładuj, potem spróbuj ponownie.

**Weryfikacja:** zamówienie nie podlega już płatności, albo `remaining_balance` wynosi `0`.

## 7. Pobranie zamówienia i śledzenie

**REST:** `GET /api/v1/customer/shipping-orders/{id}` — [Podręcznik 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"
```

**GraphQL:** `customerShippingOrderShow` ([Podręcznik GraphQL](/api/graphql/documentation#/customer/customerShippingOrderShow))

Gdy zamówienie ma `tracking_number`, publiczne śledzenie (bez tokenu):

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

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

**Weryfikacja:** szczegół to zamówienie tego klienta. Publiczne śledzenie je znajduje, gdy numer już istnieje.

## 8. Konfiguracja powiadomień o zdarzeniach

| Ustawienie | Zdarzenie | Kiedy |
|---|---|---|
| `order_create_webhook_url` | `order.created` | Zapisać `id` i numery śledzenia |
| `tracking_event_webhook_url` | `tracking.event` | Oś czasu |
| `order_status_change_webhook_url` | `order.status_change` | Status w Twoim systemie |

**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)).

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:** jedno testowe utworzenie daje `order.created` z tym `id`.

## 9. Anulowanie zamówienia testowego

**REST:** `POST /api/v1/customer/shipping-orders/{id}/cancel`

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

**Weryfikacja:** drugie anulowanie jest bezpieczne, albo API mówi, że zamówienie jest już anulowane. `422` oznacza, że tego statusu nie można anulować.

## Lista testów

Użyj testowego `reference` takiego jak `DEV-SHIP-001`:

- [ ] Lista usług nie jest pusta; zapisałeś jeden `service_code`.
- [ ] Konfiguracja zwraca magazyny / opakowania dla tej usługi.
- [ ] Wycena zwraca cenę (albo wyraźną flagę wymaganej wyceny).
- [ ] Utworzenie zwraca `id`; ten sam `Idempotency-Key` nie tworzy drugiego zamówienia.
- [ ] Płatność się udaje, **albo** potwierdziłeś, że portfel trzeba doładować (`402`).
- [ ] Szczegół pokazuje zamówienie tego klienta.
- [ ] Publiczne śledzenie znajduje przesyłkę, gdy numer śledzenia już istnieje.
- [ ] Anulowanie się udaje, **albo** potwierdziłeś, że tego statusu nie można anulować.
