Aller au contenu principal

Machine à états des commandes

Le module fait évoluer la commande à travers une petite machine à états déterministe. Les plateformes nomment ces états différemment, mais le modèle logique est universel — et le contrat de traitement l'est aussi.

États

État logiqueEntré lorsque…Terminal ?Ce que voit le client
PENDING_PAYMENTLa commande est créée, en attente du webhookNonPage de paiement hébergée (puis chargement)
PAIDpayment.succeeded, montant réconciliéOuiPage de confirmation de commande
PAYMENT_ERRORpayment.failed ou payment.declinedOuiPage d'erreur avec lien de nouvelle tentative
CANCELEDAction admin ou expiration de sessionOuiPage d'erreur si le client revient tardivement

Transitions

[début] ──validateOrder()──→ PENDING_PAYMENT

┌──────────────┼──────────────┐
▼ ▼ ▼
PAYMENT_ERROR CANCELED PAID
(payment.failed) (admin / timeout) (payment.succeeded)

┌─────┘

(webhook en doublon
→ no-op, reste PAID)

Chaque état terminal est un puits : dès qu'une transaction est en PAID, PAYMENT_ERROR ou CANCELED, aucun webhook ultérieur ne doit la faire muter. L'idempotence est le contrat.

Correspondance des états par plateforme

État logiquePrestaShopShopifyShopware 6
PENDING_PAYMENTPS_OS_BANKWIREfinancial_status = pendingÉtat : in_progress
PAIDPS_OS_PAYMENTfinancial_status = paidÉtat : paid
PAYMENT_ERRORPS_OS_ERRORfinancial_status = voidedÉtat : failed
CANCELEDPS_OS_CANCELEDfinancial_status = voidedÉtat : cancelled

Contrat d'idempotence

Les webhooks peuvent être livrés plusieurs fois : coupures réseau, nos propres relances, rejeux manuels depuis la Console. Votre gestionnaire doit court-circuiter le traitement si la commande est déjà dans un état terminal :

if order.state == PAID and event == payment.succeeded:
return 200 # no-op, déjà traité

LodinPay peut ainsi relancer agressivement sans risque, et toute une famille de bugs subtils liés aux doubles écritures disparaît de votre côté.

Cas limites

  • Le webhook arrive avant que la commande soit committée. Mettez en place une petite boucle de réessai sur la recherche de la commande (3 × 1 s) avant de renvoyer 500. Nous relancerons, et votre prochain essai trouvera la commande.
  • Montant incohérent. Si webhook.amount diffère de order.amount de plus de 0.01, rejetez avec 500. N'appliquez surtout pas silencieusement le nouveau montant — c'est presque toujours une tentative d'altération ou un bug amont qui mérite enquête.
  • payment.failed tardif après PAID. Cela ne devrait pas arriver, mais le cas échéant : conservez PAID, journalisez l'anomalie, remontez au support avec le transactionId.
  • Commande annulée par un admin, suivie d'un payment.succeeded. Lodin remboursera automatiquement. Conservez CANCELED, journalisez l'anomalie.

Voir aussi