# Etichette corriere

Acquistare una spedizione corriere: elencare i metodi → quotare → creare l’etichetta → scaricare il PDF → tracciare → ricevere gli eventi → annullare (o chiudere la giornata).

Questa guida documenta esclusivamente le operazioni seguenti. Eseguitele in sequenza.

Sostituite `YOUR_HOST`, `ACCESS_TOKEN` e `shipping_method` con i vostri. Gli id dei metodi differiscono per account — non codificateli mai.

## 1. Accesso

```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 usa la stessa intestazione su `POST /api/graphql`.

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

**Verifica:** il login restituisce `access_token`. Una chiamata successiva senza di esso restituisce `401`.

## 2. Elencare i metodi di spedizione

**REST:** `POST /api/v1/labelservice/getShippingMethodList` — [Manuale 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}'
```

Ogni riga ha:

| Campo | Uso |
|---|---|
| `id` | `shipping_method` in ogni chiamata successiva |
| `name` | Nome visualizzato |
| `unique_identifier` | Codice stabile |
| `options.signature_option` | Firma disponibile |
| `options.insurance_option` | Assicurazione disponibile |
| `options.multi_package` | Più di un pezzo |

**GraphQL:** `labelserviceGetShippingMethodList` ([Manuale GraphQL](/api/graphql/documentation#/labelservice/labelserviceGetShippingMethodList)) (scalare JSON).

**Verifica:** l’elenco non è vuoto. Avete scelto un `id` e sapete se quel metodo consente firma, assicurazione e più colli. Un elenco vuoto significa che nessun metodo è abilitato sull’account.

## 3. Quotare

Prova a vuoto. Si chiede un prezzo al corriere; non si prenota nulla. Il corpo ha la stessa forma della creazione. `shipping_method` è obbligatorio.

**REST:** `POST /api/v1/labelservice/rate` — [Manuale 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.

Per più di un pezzo, inviate `packages: [{ ref, weight, length, width, height, weight_unit, dimension_unit }]`. `shipping_from` (id rubrica) o `shipping_from_code` può sostituire il blocco `sender_*`.

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

**Verifica:** `result` è true e avete un prezzo (e i giorni di transito, se il corriere li invia). Se non c’è tariffa, correggete destinazione / collo / metodo **prima** di creare.

## 4. Creare l’etichetta

Questo prenota la spedizione con il corriere.

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

Stesso corpo del passo 3. Inviate `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` ([Manuale GraphQL](/api/graphql/documentation#/labelservice/labelserviceSubmitOrder)).

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

| Campo | Uso |
|---|---|
| `id` | Id ordine Superroute — download e annullamento |
| `tracking_numbers` | Dateli all’acquirente; il tracking li accetta |
| `external_id` | Id spedizione del corriere |
| `shipping_price` | Importo addebitato |

**Verifica:** `tracking_numbers` non è vuoto. Conservate `id` e i numeri. Lo stesso `Idempotency-Key` non deve comprare una seconda etichetta.

## 5. Scaricare il PDF

**REST:** `POST /api/v1/labelservice/getShippingLabel` — [Manuale 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` può essere `ORDER_ID`, `TRACKING_NUMBER` o `REF`. `base64: 0` invia un PDF. Questa è l’**etichetta ufficiale del corriere**. Il numero di pezzi è fissato dalla prenotazione.

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

**Verifica:** il PDF si apre e mostra il codice a barre / numero di tracking del corriere del passo 4. Stampate una copia di prova e buttatela — non consegnate un’etichetta di prova a un corriere.

## 6. Tracciare

Nessun token. Usate un numero da `tracking_numbers`:

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

[Manuale REST](/api/documentation#/operations/getPublicTracking) · [Manuale 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` è true quando gli eventi arrivano dal corriere. Diramatevi su `tracking_event_status_id` / `tracking_event_key`, non su `description`. L’evento più recente è primo in `data`. Gli eventi iniziali possono ancora essere «informazioni inviate» finché il corriere non scansiona il collo. `500` è consegnato; `proofs[]` può allora includere firma (`type` `1`) o foto (`type` `2`).

**Verifica:** la ricerca restituisce la spedizione appena creata. Un numero sconosciuto è `result: false` / 404.

## 7. Configurare le notifiche degli eventi

| Impostazione | Evento | Quando |
|---|---|---|
| `order_create_webhook_url` | `order.created` | Persistere `id` e numeri di tracking del corriere |
| `tracking_event_webhook_url` | `tracking.event` | Scansioni del corriere, in consegna, consegnato |
| `order_status_change_webhook_url` | `order.status_change` | Stato nel vostro sistema |
| `order_cancel_failed_webhook_url` | `order.cancel_failed` | Annullamento rifiutato perché il corriere ha già il collo |

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

**GraphQL:** `webhookSettingsUpdate` ([Manuale 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
  }'
```

Verificate **v2** sul corpo grezzo: `HMAC_SHA256(timestamp + "." + raw_body, secret)` contro `X-Webhook-Signature-V2`. Deduplicate su `X-Webhook-Event-Id`. Rispondete **2xx in meno di 3 secondi**.

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

**Verifica:** un `submitOrder` di prova produce `order.created` con quei `tracking_numbers`. Un segreto errato è `401` dal vostro ricevitore.

## 8. Annullare

Solo finché il corriere lo consente ancora (di solito prima del ritiro).

**REST:** `POST /api/v1/labelservice/cancelShippingLabel` — [Manuale 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` ([Manuale GraphQL](/api/graphql/documentation#/labelservice/labelserviceCancelShippingLabel)).

Un corriere che ha già il collo rifiuterà — quello è `order.cancel_failed`.

**Verifica:** un secondo annullamento è sicuro. Il tracking non tratta più la spedizione come attiva.

## 9. Fine giornata (solo se questo metodo lo richiede)

Alcuni corrieri richiedono un manifesto giornaliero.

**REST:** `POST /api/v1/labelservice/endofday` — [Manuale 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` ([Manuale GraphQL](/api/graphql/documentation#/labelservice/labelserviceEndofday)).

Chiamatelo una volta dopo l’ultima etichetta della giornata di spedizione. Saltate questo passo quando il metodo del passo 2 non ha tale requisito.

**Verifica:** il corpo di successo (o la conferma del corriere) elenca le etichette di oggi. Eseguite prima su un metodo di prova.

## Elenco di verifica

Usate una destinazione che controllate e un metodo annullabile:

- [ ] L’elenco metodi non è vuoto; avete catturato un `id`.
- [ ] La tariffa restituisce un prezzo per quel metodo e destinazione.
- [ ] Submit restituisce `tracking_numbers`; lo stesso `Idempotency-Key` non compra una seconda etichetta.
- [ ] Il PDF dell’etichetta si apre e mostra il numero di tracking del corriere.
- [ ] Il tracking pubblico trova la spedizione con quel numero.
- [ ] Arriva `order.created`; la firma v2 verifica.
- [ ] L’annullamento riesce, **oppure** avete confermato che questo metodo non si può annullare dopo la prenotazione.
- [ ] Se il metodo richiede la fine giornata, una prova termina senza errore.
