Impression fiable des commandes : idempotence, reprises et imprimante hors ligne

L'impression a l'air d'un simple appel jusqu'au premier ticket en double. Trois décisions couvrent presque tous les incidents en production : répondre au webhook d'abord, clé d'idempotence, politique hors ligne.

Un appel d'impression tient en une requête HTTP, ce qui fait passer l'impression des commandes pour l'intégration la plus simple de la feuille de route. Elle le reste jusqu'au soir où un client reçoit deux tickets pour une commande, ou jusqu'au soir où quarante commandes n'arrivent jamais en cuisine sans que personne ne s'en aperçoive avant la fermeture.

Réponse courte : trois décisions évitent presque tous les incidents en production. Répondez au webhook avant d'imprimer, donnez à chaque travail une clé stable pour qu'un doublon soit détectable, et décidez explicitement de ce qui se passe quand l'imprimante est hors ligne. Aucune n'est difficile, et les trois s'ajoutent plus facilement maintenant qu'après le premier incident.

Les trois modes de défaillance

Symptôme Cause réelle Correctif
Le même ticket sort deux fois La plateforme a réessayé un webhook lent, ou un statut a été réappliqué Une clé stable par commande
Des commandes jamais imprimées, sans alerte L'imprimante était hors ligne et l'échec a été avalé Une politique hors ligne explicite et une alerte
Le paiement traîne, ou expire L'appel d'impression tourne dans la requête qui confirme la commande Répondre d'abord, imprimer ensuite

Les trois sont liés. Un appel d'impression lent provoque la reprise qui provoque le doublon : corriger le premier supprime l'essentiel du deuxième.

1. Répondre au webhook avant d'imprimer

Shopify, WooCommerce, Stripe et toute plateforme sérieuse réessaient quand votre endpoint est lent ou renvoie une erreur. Si votre handler imprime de façon synchrone et que l'API d'impression prend deux secondes, vous avez construit un générateur de reprises.

export default async function handler(req, res) {
  const order = req.body;

  // Acquitter tout de suite : la plateforme cesse de réessayer.
  res.status(200).end();

  // Puis imprimer, hors de la requête que la plateforme attend.
  await printOrder(order).catch((err) => {
    logger.error({ err, orderId: order.id }, 'echec impression');
    alertOps(order);
  });
}

La règle : le budget de timeout de la plateforme et la latence de votre imprimante ne doivent jamais partager une échéance. C'est pour cette raison que les connecteurs Shopify et WooCommerce n'impriment pas à l'intérieur de la transaction du webhook.

2. Donner une clé à chaque travail

L'idempotence n'est pas ici un luxe de systèmes distribués. Une commande peut légitimement déclencher votre handler plusieurs fois : un callback de passerelle qui arrive en double, un commerçant qui réapplique un statut payé à la main, une reprise de webhook qui a couru contre votre acquittement.

Mettez une valeur stable dans origin, et stockez un drapeau imprimé de votre côté :

async function printOrder(order) {
  const key = `commande-${order.id}`;
  if (await alreadyPrinted(key)) return;

  await fetch(`https://www.expedy.fr/api/v2/printers/${PRINTER_UID}/print`, {
    method: 'POST',
    headers: {
      Authorization: `${process.env.API_SID}:${process.env.API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ printer_msg: buildTicket(order), origin: key }),
  });

  await markPrinted(key);
}

Deux éléments gagnent leur place. Le drapeau local arrête le doublon avant le réseau, et origin rend visible dans l'historique d'impression un doublon passé au travers, ce qui transforme « je crois que ça a imprimé deux fois » en une question à laquelle vous pouvez répondre.

Écrivez le drapeau dans la même transaction que le reste du traitement de la commande si vous le pouvez. Un drapeau écrit après un appel HTTP réussi laisse une petite fenêtre, et c'est exactement dans cette fenêtre que naissent les tickets en double.

3. Choisir la politique hors ligne délibérément

Demandez-vous ce qui doit se passer quand l'imprimante n'a plus de papier, est débranchée, ou se trouve derrière un routeur qui a redémarré. Il n'y a que trois réponses raisonnables, et la mauvaise consiste à ne pas choisir :

  • Mettre en file et livrer plus tard. Correct pour les étiquettes d'expédition et les documents d'entrepôt, où un ticket sorti vingt minutes plus tard sert encore.
  • Réessayer brièvement, puis alerter. Correct pour les tickets de cuisine, où un ticket sorti vingt minutes plus tard est pire qu'inutile, le service étant passé à autre chose.
  • Alerter immédiatement. Correct quand un humain peut agir, et seule option qui transforme une perte silencieuse en problème connu.

Quel que soit votre choix, implémentez l'alerte. Ce qui sépare la contrariété du mauvais service, ce n'est pas la panne d'imprimante, c'est de savoir si quelqu'un l'a su.

Ce qu'il ne faut pas construire

Les équipes reconstruisent régulièrement une mécanique que l'API fournit déjà. Avant d'écrire une file, un ordonnanceur et une échelle de reprises, regardez les garanties de livraison dont vous disposez : une API push qui retient le travail et le livre à la reconnexion de l'appareil rend inutile l'essentiel de ce code. Écrivez les parties qui encodent vos règles métier, comme le choix de l'imprimante selon la commande, et laissez la fiabilité du transport au transport.

Tester avant la production

Trois tests attrapent l'essentiel de ce qui arrive en production :

  1. Rejouez deux fois le même webhook. Un seul ticket doit sortir.
  2. Débranchez l'imprimante, puis envoyez une commande. Votre politique doit se produire visiblement, alerte comprise.
  3. Commandez un produit avec un emoji dans son nom. Les imprimantes thermiques parlent ESC/POS ; translittérez avant l'envoi, sinon vous le découvrirez au pire moment. La référence de mise en page documente ce que le format accepte.

Faites les trois contre une vraie imprimante, pas un bouchon. Les pannes intéressantes sont physiques.

Créez un compte gratuit et câblez ces trois tests dans votre branche d'intégration avant que la première commande client ne la traverse.

À lire aussi : imprimer les commandes Shopify et WooCommerce montrent où se trouvent ces accroches selon la plateforme.

FAQ

Pourquoi la même commande s'imprime-t-elle parfois deux fois ?

Presque toujours parce que la plateforme a réessayé un webhook auquel votre endpoint a répondu lentement, ou parce qu'un statut payé a été appliqué plusieurs fois. Acquittez le webhook avant d'imprimer et donnez à chaque travail une clé stable comme l'identifiant de commande.

Faut-il imprimer dans le handler du webhook ?

Non. Répondez 200 immédiatement, puis imprimez hors de cette requête. Partager une échéance entre le timeout de la plateforme et la latence de l'imprimante est ce qui génère les reprises, et les reprises génèrent les doublons.

Que doit-il se passer quand l'imprimante est hors ligne ?

Choisissez une politique délibérément : mettre en file et livrer plus tard, réessayer brièvement puis alerter, ou alerter tout de suite. La file convient aux étiquettes d'expédition, l'alerte aux tickets de cuisine. La seule mauvaise réponse est de ne rien définir.

Dois-je construire ma propre file d'impression ?

Généralement non. Une API push qui retient le travail et le livre à la reconnexion de l'appareil le fait déjà. Écrivez le code qui encode vos règles métier, comme le routage vers l'imprimante, pas une deuxième couche de transport.

Comment tester la fiabilité avant la mise en production ?

Rejouez deux fois un webhook et attendez un seul ticket, débranchez l'imprimante et vérifiez que votre politique hors ligne et l'alerte se déclenchent, et commandez un produit avec un emoji dans son nom. Faites les trois sur une vraie imprimante.