# Services d’expédition

Utilisez les services d’expédition de l’entreprise de logistique : lister les services → charger la configuration d’un service → estimer → créer la commande → payer → consulter → suivre → annuler une commande de test.

Ce guide documente uniquement les opérations suivantes. Exécutez-les dans l’ordre. L’authentification utilise un compte **client**.

Remplacez `YOUR_HOST` et `ACCESS_TOKEN` par les valeurs de votre environnement.

## 1. Authentification (client)

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

Utilisez `access_token` ainsi :

```
Authorization: Bearer ACCESS_TOKEN
```

GraphQL utilise le même en-tête sur `POST /api/graphql`.

**Vérification :** la connexion renvoie `access_token`. Un appel ultérieur sans lui renvoie `401`.

## 2. Lister les services

**REST :** `GET /api/v1/customer/shipping-orders/services` — [Manuel 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` ([Manuel GraphQL](/api/graphql/documentation#/customer/customerShippingOrderServices))

Chaque ligne a `service_code`, `name`, `offer_pickup`, `allow_warehouse_delivery`. Une liste vide signifie qu’aucun service n’est attribué à ce client.

**Vérification :** vous avez relevé un `service_code` (exemple ci-dessous : `intl_express`).

## 3. Charger la configuration de ce service

**REST :** `GET /api/v1/customer/shipping-orders/services/{serviceCode}/config` — [Manuel 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` ([Manuel GraphQL](/api/graphql/documentation#/customer/customerShippingOrderServiceConfig))

**Vérification :** vous avez au moins un `id` d’entrepôt si ce service autorise le dépôt en entrepôt. `403` signifie que ce client n’a pas droit à ce service.

## 4. Estimer

**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` (dépôt en entrepôt) ou `pickup` (l’entreprise enlève). Faites correspondre ce que l’étape 3 indiquait comme autorisé pour le service.

**Vérification :** `result` est true et vous avez un prix (ou un indicateur « devis nécessaire » pour une tarification manuelle). Ne créez pas encore si la destination ou le colis est refusé.

## 5. Créer la commande

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

Envoyez `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
    }]
  }'
```

**Vérification :** la réponse a un `id` de commande. Stockez `id`, `tracking_number` / `reference_number` lorsqu’ils sont présents.

## 6. Payer

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

Optionnel : d’abord `GET /api/v1/customer/shipping-orders/{id}/payment-info`. Un `402` signifie que le solde ne couvre pas le montant — rechargez, puis réessayez.

**Vérification :** la commande n’est plus payable, ou `remaining_balance` est `0`.

## 7. Consulter la commande et le suivi

**REST :** `GET /api/v1/customer/shipping-orders/{id}` — [Manuel 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` ([Manuel GraphQL](/api/graphql/documentation#/customer/customerShippingOrderShow))

Lorsqu’une `tracking_number` figure sur la commande, suivi public (pas de jeton) :

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

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

**Vérification :** le détail est la commande de ce client. Le suivi public la trouve dès qu’un numéro existe.

## 8. Configurer les notifications d’événements

| Réglage | Événement | Quand |
|---|---|---|
| `order_create_webhook_url` | `order.created` | Persister `id` et les numéros de suivi |
| `tracking_event_webhook_url` | `tracking.event` | Chronologie |
| `order_status_change_webhook_url` | `order.status_change` | Statut dans votre système |

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

**GraphQL :** `webhookSettingsUpdate` ([Manuel GraphQL](/api/graphql/documentation#/webhooks/webhookSettingsUpdate)).

Vérifiez **v2** sur le corps brut : `HMAC_SHA256(timestamp + "." + raw_body, secret)` contre `X-Webhook-Signature-V2`. Dédupliquez sur `X-Webhook-Event-Id`. Répondez **2xx en moins de 3 secondes**.

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

**Vérification :** une création de test produit `order.created` avec cet `id`.

## 9. Annuler une commande de test

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

**Vérification :** une seconde annulation est sans danger, ou l’API indique que la commande est déjà annulée. `422` signifie que ce statut ne peut pas être annulé.

## Liste de tests

Utilisez un `reference` de test comme `DEV-SHIP-001` :

- [ ] La liste des services n’est pas vide ; vous avez relevé un `service_code`.
- [ ] La configuration renvoie entrepôts / emballage pour ce service.
- [ ] L’estimation renvoie un prix (ou un indicateur clair de devis nécessaire).
- [ ] La création renvoie un `id` ; le même `Idempotency-Key` ne crée pas une seconde commande.
- [ ] Le paiement réussit, **ou** vous avez confirmé que le solde doit être rechargé (`402`).
- [ ] Le détail affiche la commande de ce client.
- [ ] Le suivi public trouve l’envoi dès qu’un numéro de suivi existe.
- [ ] L’annulation réussit, **ou** vous avez confirmé que ce statut ne peut pas être annulé.
