M

Suivez nous

Magento 2 & Odoo : Détection et correction d’un bug complexe de synchronisation de commandes avec Hipay

Architecture initiale et motivation

Au départ, les commandes étaient récupérées automatiquement par notre ERP Odoo. Odoo appelait l’API Magento et récupérait les commandes. Pour plus de fluidité, nous avons décidé de pousser nous-mêmes les commandes : à l’événement after_save d’une commande, si elle était dans un état éligible, nous la publiions dans notre propre file d’attente (RabbitMQ & file custom).

Les files sont traitées par un supervisor sur notre serveur back-office. Lors de la lecture de la file, nous prenons la commande et l’envoyons à Odoo (ou plus précisément, pour éviter d’avoir deux processus différents, nous notifions Odoo de la commande afin qu’il aille chercher uniquement celle-ci).

À noter : Odoo continue de récupérer les commandes à intervalles réguliers, en filet de sécurité au cas où notre côté plante.

Pourquoi la file d’attente ? Nous avons choisi d’utiliser une file parce que, logiquement, nous évitons au maximum les appels API synchrones : l’API peut être lente ou indisponible, et dans ce cas, l’utilisateur qui passe commande en pâtirait. Quand on traite des commandes e-commerce — surtout en période de forte affluence —, on ne demande pas à un client d’attendre pendant qu’on appelle Odoo. On met le travail en file et on le traite de façon asynchrone, pour garder une expérience utilisateur fluide et un front réactif.

Tout fonctionnait parfaitement en pré-production. Nous avons déployé en production.

Le bug : annulation intempestive de commandes en production

Après une phase de pré-production sans accroc, nous avons déployé en production. Le lendemain du déploiement, nous avons constaté que les commandes payées via Hipay étaient annulées à tort. Le cron Hipay, qui tourne 24h après la commande (délai configurable), annulait les commandes qui n’étaient pas en statut “pending” — mais nous avons trouvé de nombreuses commandes en statut “new” et “pending” qui étaient pourtant payées avec Hipay, et même facturées.

En consultant la table sales_order_status_history, nous avons vu les changements de statut, mais aucune trace d’un retour à “pending” — une anomalie flagrante. Au début, nous avons suspecté un bug Hipay, mais en creusant, nous avons trouvé de nombreuses commandes en état “new” et statut “pending” qui étaient pourtant payées avec Hipay et même facturées. La table sales_order_status_history montrait bien les changements de statut pour ces commandes, mais aucune trace d’un retour à “pending” — quelque chose réinitialisait activement ces statuts.

Nous avons réagi immédiatement en modifiant le cron Hipay pour qu’il vérifie si la commande était facturée avant annulation, ce qui a stoppé l’hémorragie. L’analyse a montré que les premiers cas concernés apparaissaient une heure après le déploiement, ce qui pointait fortement vers notre sync custom. Nous avons regardé le code d’envoi des commandes vers Odoo, qui semblait le coupable le plus probable, même si nous ne comprenions pas encore totalement pourquoi. Nous avons commenté ce code en production. Résultat : plus de problème ! (Nous avons dû attendre avant de crier victoire, car toutes les commandes n’étaient pas concernées, bien sûr.)

Cause racine : limites de transaction et conditions de concurrence

Cause initiale

Quand Hipay traite un paiement, il déclenche un webhook qui met à jour la commande, la facture et sauvegarde le tout — le tout dans une seule transaction base de données. Notre sync custom écoutait l’événement after_save, donc publiait la commande dans la file presque instantanément. Le consommateur de la file, lancé via supervisor, rechargeait alors la commande et la traitait avant que la transaction ne soit commitée.

Résultat : le consommateur voyait — et sauvegardait — un état obsolète de la commande, réinitialisant en pratique le statut à “new_pending” alors même que Hipay avait finalisé le paiement et la facturation. Le supervisor était trop rapide : le consommateur chargeait la commande avant que son changement de statut ne soit commité ! Hipay finalisait ensuite sa sauvegarde avec les bonnes infos, mais le consommateur, qui avait chargé une commande en “new_pending”, venait re-sauvegarder le flag ‘send_to_odoo’ en même temps que le reste. Comme nous sauvegardons toute la commande et pas seulement le champ, Magento re-sauvegardait l’ancien état et le statut.

La correction

Au lieu d’utiliser l’événement after_save, il faut écouter l’événement after_commit. L’événement after_commit n’est déclenché qu’après que la transaction soit totalement validée, garantissant que tout consommateur ou processus background verra toujours l’état cohérent et à jour de la commande. Cela supprime la condition de concurrence où un consommateur pourrait recharger et re-sauvegarder un état obsolète de commande.

Enseignements techniques et bonnes pratiques

  • Le choix de l’événement est crucial : Dans Magento 2 (et les systèmes similaires), préférez toujours after_commit à after_save pour les actions qui dépendent de l’état final et validé d’une entité — surtout quand on intègre des files ou des systèmes externes.
  • Consommateurs de file et transactions : Un traitement de file quasi-synchrone peut introduire des conditions de concurrence subtiles si les consommateurs agissent avant la fin des transactions. Concevez votre architecture en respectant les limites de transaction.
  • Le traitement asynchrone est indispensable : La synchronisation en temps réel entre un e-commerce et un ERP nécessite une gestion rigoureuse des flux asynchrones. Les files (comme RabbitMQ) sont fondamentales pour ne pas bloquer les actions utilisateur, mais il faut les coupler à une gestion correcte des événements.
  • Les stratégies hybrides améliorent la résilience : Combiner des mises à jour en temps réel via file et un polling périodique (comme le fait Odoo) crée un filet de sécurité robuste, garantissant qu’aucune commande ne sera perdue même si la sync principale plante.
  • Surveillance et détection des timing : Quand vous debuggez, vérifiez non seulement le code, mais aussi le timing et le contexte transactionnel — surtout après un déploiement. Une anomalie qui apparaît exactement une heure après le déploiement n’est jamais une coïncidence.
  • Complexité des modules de paiement et des intégrations : Les modules de paiement comme Hipay opèrent dans des contextes transactionnels multi-étapes. Toute sync custom doit respecter ces limites pour ne pas corrompre l’état du paiement.

Conclusion

Ce cas met en lumière comment un petit choix architectural (l’écouteur d’événement) peut avoir de grandes conséquences en production. En passant de after_save à after_commit, nous avons résolu un bug critique de réinitialisation de statut de commande dans notre intégration Magento 2, Odoo et Hipay. La motivation initiale — éviter les appels API synchrones pour garder le front réactif — était juste ; l’implémentation nécessitait d’être affinée. Cette expérience souligne l’importance de bien comprendre les limites de transaction, le timing des événements et la nécessité d’une surveillance robuste dans les plateformes e-commerce. Testez toujours vos scénarios d’intégration dans des conditions réalistes, respectez la sémantique des transactions, et structurez vos contenus pour expliquer clairement le problème et la solution — afin d’aider les autres à éviter des pièges similaires dans leurs propres intégrations Magento et ERP.