L'idempotence : ne facturez jamais deux fois
L'idempotence des paiements : comment l'en-tête Idempotency-Key et l'externalId évitent de débiter deux fois un client quand le réseau coupe.
Votre serveur envoie une demande de paiement. La connexion tombe avant que la réponse n'arrive. Vous ne savez pas si l'opération est passée. Que faites-vous ?
Si vous réessayez, vous risquez de débiter deux fois. Si vous ne réessayez pas, vous risquez de perdre une vente. Ce dilemme a une solution, et elle tient en une en-tête HTTP.
Le problème n'est pas rare
On imagine volontiers que ce cas est théorique. Il ne l'est pas. Un délai d'attente dépassé, un redémarrage de conteneur au mauvais moment, un client mobile qui bascule du Wi-Fi à la 4G, une file de tâches qui rejoue un message : chacun de ces incidents produit exactement la même situation.
À l'échelle de quelques centaines de paiements par jour, cela se produit. À l'échelle de quelques milliers, cela se produit tous les jours.
La clé d'idempotence
Toute création accepte une en-tête Idempotency-Key — le guide Idempotence de la documentation API en fixe les règles. La règle est simple : deux appels portant la même clé produisent une seule ressource.
curl -X POST 'https://api-psp.charipay.ma/v1/payment-links' \
-H 'X-CHARI-PAY-API-KEY: chari_sk_live_...' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd-2026-1042' \
-d '{ "amount": 249.00, "description": "Commande #1042" }'Rejouez cet appel autant de fois que vous voulez : vous obtiendrez toujours le même lien de paiement. Pas un deuxième. Pour le vérifier vous-même, remplacez la clé par une clé de test (chari_sk_test_…) : Commencer en mode test ouvre la sandbox dès l'inscription, sans validation à attendre.
La clé doit être déterministe et propre à l'opération. La référence de votre commande est un excellent candidat : vous la connaissez, elle ne change pas, et elle est unique chez vous. Un identifiant aléatoire tiré à chaque tentative, en revanche, ne sert à rien — c'est exactement ce que la clé est censée éviter.
Les liens de paiement vont plus loin
Sur les liens de paiement, le champ externalId porte la même garantie, de façon persistante : il est unique par compte et par environnement.
Créez deux fois un lien avec le même externalId, et l'API renvoie le lien existant avec un code 200 au lieu de 201. La différence de code vous dit exactement ce qui s'est passé, sans que vous ayez à conserver le moindre état intermédiaire.
C'est particulièrement utile quand votre traitement est déclenché par une file de messages : le message peut être livré deux fois, votre code peut appeler deux fois, le résultat reste juste. Pour retrouver ensuite la commande derrière chaque paiement, voyez relier un paiement à sa commande.
Pour les remboursements
La référence de remboursement — le champ refundReference de la référence API des remboursements — joue le même rôle. Rejouer un remboursement avec la même référence ne rembourse pas deux fois — ce qui, sur un remboursement partiel, évite une classe entière d'incidents pénibles à démêler.
Ce que l'idempotence ne fait pas
Elle ne remplace pas la vérification de vos propres états. Si votre système peut créer deux commandes pour un même panier, aucune en-tête HTTP ne le corrigera.
Elle ne rend pas non plus vos webhooks idempotents. C'est un travail distinct, du même esprit, détaillé dans réussir son intégration des webhooks : chaque livraison porte un identifiant d'événement, stockez-le, et ignorez ce que vous avez déjà traité. Une même notification peut arriver deux fois — c'est voulu, pour qu'aucune ne se perde. Enfin, elle ne dit pas quoi faire d'une erreur : rejouer ou corriger dépend du code renvoyé, détaillé dans codes d'erreur de l'API de paiement.
Générer une bonne clé
La clé d'idempotence n'est pas un nombre aléatoire tiré au moment de l'appel — ce serait passer à côté de tout l'intérêt. Une bonne clé est déterministe : la même opération métier produit toujours la même clé.
Le motif le plus sûr : dérivez la clé de l'identifiant de l'objet métier qui déclenche le paiement. commande-8842-paiement pour l'encaissement de la commande 8842 ; client-512-abonnement pour la création de l'abonnement du client 512 ; remboursement-tx-77a1 pour un remboursement. Si votre code repasse par là — retry automatique, file de messages livrée deux fois, utilisateur pressé — la clé est identique, et l'opération unique.
À l'inverse, deux opérations réellement distinctes doivent produire deux clés distinctes : n'utilisez jamais l'identifiant du client seul, ou toutes ses commandes se partageraient une clé.
Une limite à connaître : la clé protège l'appel, pas votre logique. Si votre code crée deux commandes différentes pour un même achat, chacune portera sa clé et chacune sera encaissée. L'idempotence commence dans votre modèle de données.
L'idempotence côté webhooks
Le même principe s'applique dans l'autre sens. Une notification de paiement peut vous être livrée deux fois — c'est une garantie de fiabilité, pas un défaut. Votre traitement de webhook doit donc être idempotent lui aussi : l'identifiant d'événement joue le rôle de la clé, une table à contrainte d'unicité joue le rôle du garde-fou.
Les deux mécanismes se répondent : la clé d'idempotence empêche votre retry de créer deux paiements ; la déduplication d'événements empêche notre retry de vous faire expédier deux commandes. Une intégration robuste porte les deux, et le même incident réseau — le seul vrai coupable dans cette histoire — ne coûte plus rien à personne.
La règle en une phrase
Toute création qui engage de l'argent porte une clé d'idempotence dérivée de votre propre référence, et tout traitement de webhook déduplique par identifiant d'événement. Deux habitudes, dix lignes de code, et une classe entière de bugs qui disparaît.
Prochaine étape
Le plus parlant est de rejouer vous-même un appel avec la même clé et de comparer les réponses. Commencer en mode test demande un nom, un e-mail professionnel et le nom de l'entreprise ; après le lien d'activation reçu par e-mail et le choix du mot de passe, vous créez une clé de test depuis le portail — gratuit, sans limite de durée. Pour l'ensemble de l'intégration — clés, liens, sessions, webhooks signés —, suivez le guide API de paiement au Maroc.
Écrit par Équipe ChariPay.