# Uniorder : une API pour chaque envoi

Uniorder est un ensemble unique de points de terminaison par lequel une intégration tarife, crée, imprime, suit et annule chaque envoi du compte, quel que soit le mode d’exécution de l’envoi. Un seul devis renvoie la livraison effectuée par l’entreprise elle-même et, sur demande, chaque service d’étiquettes transporteur du compte, chacun avec un `rate_id`. La commande est créée en renvoyant le `rate_id` choisi ; aucun autre élément de la requête ne sélectionne le service.

## 1. Ce que vous pouvez construire

- **Un tunnel de paiement qui propose toutes les options d’expédition à la fois.** Le client saisit une adresse, le tunnel de paiement appelle un seul point de terminaison et la page affiche la livraison locale à côté d’UPS, de Postes Canada et de tout autre transporteur utilisé par le compte, chacun avec son prix.
- **Un connecteur de gestion des commandes ou d’ERP avec un seul chemin de code.** Les commandes de tous les canaux passent par les mêmes appels de création, de consultation, d’étiquette, de suivi et d’annulation. Le connecteur n’a pas besoin d’une logique distincte pour la livraison locale et pour les étiquettes transporteur.
- **Traitement de masse de nuit.** Jusqu’à 500 envois sont tarifés ou créés dans une seule tâche en file d’attente, et les résultats sont relus par l’identifiant de la tâche.
- **Un écran de service client.** Un agent recherche une commande, réimprime son étiquette, lit son historique de suivi et l’annule, avec les mêmes quatre appels pour chaque commande.

## 2. Ce qu’Uniorder fait pour vous

| Sans Uniorder | Avec Uniorder |
|---|---|
| Une API pour les commandes de livraison locale et une autre pour les étiquettes transporteur, chacune avec ses propres champs et réponses | Une seule forme de requête (`from_*`, `to_*`, `packages`) et une seule forme de réponse pour chaque service |
| L’intégration décide quelle API transporteur appeler | Le devis énumère chaque service ; le `rate_id` du tarif choisi décide |
| Des points de terminaison d’étiquette, de suivi et d’annulation distincts par service | `GET /label`, `GET /tracking` et `POST /cancel` fonctionnent pour chaque commande |
| Création par lot disponible uniquement pour la livraison locale | Tarification par lot et création par lot pour chaque service, synchrones ou en file d’attente |

Uniorder ne remplace pas les points de terminaison existants ; ils restent disponibles et inchangés. C’est le point d’entrée recommandé pour une nouvelle intégration.

## 3. Carte des points de terminaison, dans l’ordre où une intégration les utilise

| Étape | Objet | REST | GraphQL |
|---|---|---|---|
| 1 | Obtenir un jeton d’accès | `POST /api/v1/user/login` | `userLogin` |
| 2 | Tarifer tous les services | `POST /api/v1/uniorder/rate` | `uniorderRate` |
| 3 | Créer la commande au tarif choisi | `POST /api/v1/uniorder` | `uniorderCreate` |
| 4 | Imprimer l’étiquette | `GET /api/v1/uniorder/{orderId}/label` | `uniorderLabel` |
| 5 | Consulter la commande | `GET /api/v1/uniorder/{orderId}` | `uniorder` |
| 6 | Suivre la commande | `GET /api/v1/uniorder/{orderId}/tracking` | `uniorderTracking` |
| 7 | Annuler la commande | `POST /api/v1/uniorder/{orderId}/cancel` | `uniorderCancel` |
| — | Acheter une étiquette qui n’a pas pu être achetée à la création | `POST /api/v1/uniorder/{orderId}/label` | `uniorderPurchaseLabel` |
| — | Tarifer ou créer jusqu’à 20 lignes à la fois | `POST /api/v1/uniorder/rate/batch`, `POST /api/v1/uniorder/batch` | `uniorderRateBatch`, `uniorderCreateBatch` |
| — | Mettre en file d’attente jusqu’à 500 lignes | `POST /api/v1/uniorder/rate/batch-async`, `POST /api/v1/uniorder/batch-async`, `GET /api/v1/uniorder/jobs/{jobId}` | `uniorderRateBatchAsync`, `uniorderCreateBatchAsync`, `uniorderJob` |

[Manuel REST](/api/documentation#/paths/v1-uniorder-rate/post) · [Manuel GraphQL](/api/graphql/documentation#/orders/uniorderRate)

Les requêtes, réponses et vérifications étape par étape figurent dans le guide **Devis et commande en un seul parcours**.

## 4. Exemple : un tunnel de paiement qui propose toutes les options

Un fleuriste de Montréal vend en ligne. Au paiement, il tarife le colis une seule fois, transporteurs d’étiquettes inclus :

```bash
curl -X POST https://YOUR_HOST/api/v1/uniorder/rate \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from_name": "Fleurs du Plateau", "from_telephone": "5145550100",
    "from_address": "4500 Rue Saint-Denis", "from_city": "Montreal", "from_province": "QC", "from_country": "CA", "from_postcode": "H2J2L3",
    "to_name": "Jane Recipient", "to_telephone": "5145550199",
    "to_address": "6841 Rue Saint-Denis", "to_city": "Montreal", "to_province": "QC", "to_country": "CA", "to_postcode": "H2S2S3",
    "quote_labels": true,
    "packages": [{ "weight": 1.2, "weight_unit": 2, "length": 40, "width": 25, "height": 25, "dimension_unit": 2 }]
  }'
```

La réponse énumère un tarif `self_delivery` et un tarif `label_service` par service transporteur. Le tunnel de paiement les affiche comme options ; le client choisit la livraison locale le jour même. La commande est créée avec le `rate_id` de ce tarif et les mêmes adresses et colis :

```bash
curl -X POST https://YOUR_HOST/api/v1/uniorder \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: checkout-10045" \
  -d '{
    "rate_id": "eyJpdiI6Ik1rT2Z...",
    "ref": "WEB-10045",
    "from_name": "Fleurs du Plateau", "from_telephone": "5145550100",
    "from_address": "4500 Rue Saint-Denis", "from_city": "Montreal", "from_province": "QC", "from_country": "CA", "from_postcode": "H2J2L3",
    "to_name": "Jane Recipient", "to_telephone": "5145550199",
    "to_address": "6841 Rue Saint-Denis", "to_city": "Montreal", "to_province": "QC", "to_country": "CA", "to_postcode": "H2S2S3",
    "packages": [{ "weight": 1.2, "weight_unit": 2, "length": 40, "width": 25, "height": 25, "dimension_unit": 2 }]
  }'
```

La réponse renvoie l’`id` de la commande et ses numéros de suivi. La boutique imprime l’étiquette avec `GET /api/v1/uniorder/{orderId}/label` et affiche l’historique de `GET /api/v1/uniorder/{orderId}/tracking` sur la page de commande du client.

## 5. Exemple : un ERP qui expédie chaque nuit

Un ERP exporte les commandes du jour à 22 h 00. Il envoie les adresses à `POST /api/v1/uniorder/rate/batch-async`, lit la tâche jusqu’à ce que `status` soit `done`, sélectionne un tarif pour chaque ligne selon ses propres règles et envoie les lignes choisies à `POST /api/v1/uniorder/batch-async`. Chaque résultat porte la `reference` de la ligne, de sorte que l’ERP rapproche chaque résultat de sa propre ligne de commande. Un `rate_id` est valable 30 minutes ; la tâche de création est donc envoyée peu après la fin de la tâche de tarification.

## 6. Exemple : un écran de service client

Lors de l’appel d’un client, l’écran de l’agent appelle `GET /api/v1/uniorder/{orderId}` pour le statut et les adresses, `GET /api/v1/uniorder/{orderId}/tracking` pour l’historique et la preuve de livraison, et `POST /api/v1/uniorder/{orderId}/cancel` lorsque le client annule. Les mêmes appels s’appliquent à une commande de livraison et à une commande d’étiquette ; le champ `type` les distingue.

```bash
curl https://YOUR_HOST/api/v1/uniorder/123456/tracking \
  -H "Authorization: Bearer ACCESS_TOKEN"
```

La même commande via GraphQL ([Manuel GraphQL](/api/graphql/documentation#/orders/uniorder)) :

```graphql
query {
  uniorder(order_id: 123456)
}
```

## 7. Règles à prendre en compte dans la conception

- **Le `rate_id` décide du service.** Il est valable 30 minutes et uniquement pour le compte qui a demandé le devis.
- **La commande est tarifée au moment de sa création.** `shipping_price` est le prix facturé ; `quoted_price` est le prix du devis. Les deux peuvent différer.
- **Une commande d’étiquette n’est jamais perdue.** Lorsque l’étiquette ne peut pas être achetée à la création, la commande est conservée et la réponse est `LABEL_PURCHASE_FAILED` avec l’`id` de la commande ; l’étiquette est achetée plus tard avec `POST /api/v1/uniorder/{orderId}/label`.
- **Les nouvelles tentatives sont sûres.** Envoyez un en-tête `Idempotency-Key` à chaque appel de création ; l’annulation d’une commande déjà annulée renvoie `already_cancelled` `true`.
- **Les statuts sont uniformes.** Une commande de livraison indique `pending`, `in_transit`, `out_for_delivery`, `delivered`, `exception` ou `cancelled` ; une commande d’étiquette indique `label_pending`, `label_purchased` ou `cancelled`, et le suivi de son transporteur indique où se trouve le colis.

## 8. Liste de contrôle pour la mise en production

- [ ] Le compte dispose de l’autorisation API et le jeton est conservé sur le serveur, pas dans un navigateur.
- [ ] Les devis sont demandés avec les adresses complètes de l’expéditeur et du destinataire.
- [ ] Les commandes sont créées dans les 30 minutes suivant le devis, avec un `Idempotency-Key`.
- [ ] `LABEL_PURCHASE_FAILED` est traité en achetant l’étiquette plus tard, jamais en créant la commande une seconde fois.
- [ ] Le suivi est lu depuis `GET /api/v1/uniorder/{orderId}/tracking` ou reçu par webhooks.
- [ ] L’annulation traite `ORDER_STATUS_NOT_CANCELLABLE` et `ORDER_CANCEL_REFUSED` en laissant la commande en l’état.
