Aller au contenu

Idempotence

Sécurisez les nouvelles tentatives pour qu'un délai d'expiration ne transforme jamais un paiement en deux.

Le problème n'est pas l'échec des requêtes. Il vient du fait que certaines échouent après que le serveur a effectué le travail. Délai d'expiration, connexion interrompue ou pod redémarré pendant l'opération : votre code reçoit une erreur sans savoir si le paiement a été créé.

Une clé d'idempotence élimine cette incertitude. Vous associez la même chaîne à la première tentative et à toutes les suivantes. Wajub renvoie alors le résultat initial au lieu d'effectuer deux fois l'opération.

Le fonctionnement détaillé se trouve dans la référence API

Cette page explique comment utiliser les clés dans une intégration réelle. Pour le contrat de l'en-tête, consultez Idempotence.

L'en-tête

L'en-tête est Idempotency-Key, sans préfixe X-. Vous choisissez sa valeur dans le format que Wajub vérifie avant tout autre traitement de la requête.

RègleValeur
Caractères autorisésA-Z, a-z, 0-9 et . _ : -
LongueurDe 1 à 128 caractères
Toute autre valeur422, avec le motif dans errors.idempotency_key
PortéeLa clé, votre équipe et l'environnement, ensemble

La portée est plus importante qu'il n'y paraît. Une même clé utilisée dans la sandbox et en mode live correspond à deux clés indépendantes. Rejouer un test de la sandbox ne peut donc pas entrer en conflit avec la production. Pour une requête contenant X-Sync, le compte connecté fait aussi partie de la portée. Une même clé peut ainsi désigner « cette commande » chez plusieurs vendeurs d'un panier de marketplace.

Une espace, une barre oblique ou un # provoque le rejet immédiat de la clé. Un numéro de commande comme #4172 ou un UUID entre accolades échoue donc avant même l'examen du paiement.

Endpoints qui la prennent en charge

L'idempotence ne se limite pas aux paiements. Tous les endpoints de création ci-dessous lisent l'en-tête.

EndpointRésultat d'une répétition
POST /paymentsLa réponse initiale et, après 24 heures, toujours le même paiement
POST /payments/{id}Le résultat initial du traitement
POST /transfersLe transfert initial
POST /refundsLe remboursement initial
POST /customersLe client initial
POST /beneficiariesLe bénéficiaire initial
POST /invoicesLa facture initiale
POST /linksLe lien de paiement initial
POST /webhooksL'endpoint initial, secret inclus

Le SDK Node envoie déjà une clé, et c'est le piège

Si vous utilisez @wajub/node sans transmettre idempotencyKey, le SDK en génère une pour chaque requête autre que GET et DELETE. Il crée à chaque fois un nouvel UUID aléatoire.

L'en-tête est donc toujours présent, mais ne vous protège pas. La nouvelle tentative utilise une clé différente de la première, précisément la situation que l'idempotence doit éviter. La simple présence de l'en-tête ne suffit pas. Sa valeur doit être identique lors des deux tentatives.

curl https://api.wajub.com/payments \
  -H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
  -H "Idempotency-Key: ORDER-4172" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "currency": "XAF",
    "email": "buyer@example.com",
    "reference": "ORDER-4172"
  }'

Choisir le contenu de la clé

La clé doit survivre à votre processus. Générez-la avant la première tentative, enregistrez-la avec la commande et relisez-la à chaque nouvelle tentative. Une clé créée avec Date.now() ou un nouvel UUID au moment de l'appel change à chaque tentative, ce qui revient à ne pas avoir de clé.

StratégieCléCas adapté
Une commande, une seule tentativeORDER-4172La plupart des checkouts. Simple et sans risque d'erreur
Une commande, tentatives numérotéesORDER-4172-A2Vous permettez au client de réessayer volontairement après un échec
UUID enregistré5b2c… enregistré avec la commandeLes identifiants de vos commandes ne doivent pas être exposés

Utilisez la deuxième stratégie lorsqu'un paiement Mobile Money échoue et que le client souhaite réessayer. L'augmentation du compteur est une action volontaire. Toutes les nouvelles tentatives accidentelles au sein d'un même essai aboutissent toujours à un seul paiement.

La clé appartient à la commande, pas à la requête

Enregistrez-la sur la ligne correspondant à la commande à payer. Si votre logique de nouvelle tentative doit inventer la clé, elle se trouve au mauvais endroit.

Ce qui constitue une même requête

Pour POST /payments, Wajub calcule le hash d'un instantané de votre payload et le conserve avec la clé. Les champs suivants font partie de cet instantané.

amount, currency, email, phone, name, customer_id, description, reference, callback, expires.in, theming, items, telemetry.

Tous les autres champs, y compris metadata, en sont exclus. Réutiliser la même clé avec un nouvel objet metadata renvoie le paiement initial sans le modifier. Ne placez donc pas dans les métadonnées une information que la répétition doit mettre à jour.

Si un champ de l'instantané change, la clé et le payload ne correspondent plus. Wajub refuse la requête au lieu de deviner lequel des deux est correct.

422, même clé avec un montant différent
{
"code": 422,
"status": "Unprocessable Content",
"message": "This Idempotency-Key was already used with a different request payload.",
"errors": {
"idempotency_key": [
"This Idempotency-Key was already used with a different request payload."
]
}
}

Cette erreur provient toujours d'un défaut dans votre code, jamais d'un problème temporaire. Vous avez réutilisé une clé qui aurait dû être renouvelée ou modifié un montant sans changer la clé. Ne réessayez pas cette requête.

Trois couches, dont une seule est permanente

POST /payments utilise plusieurs niveaux de protection. Identifier la couche qui répond permet de comprendre le résultat reçu.

CoucheDuréeFonction
Verrou10 secondesDeux requêtes simultanées avec la même clé attendent au lieu de se concurrencer
Cache24 heuresLa réponse initiale est renvoyée à l'identique, statut inclus
Index uniquePermanenteLa base de données refuse un second paiement avec cette clé, quel que soit l'état du cache

Après 24 heures, le cache a disparu, mais pas l'index. Une répétition renvoie donc toujours le même paiement au lieu d'en créer un second. Une différence est importante : de nouveaux authorization_url et authorization_token sont générés, car une session de checkout ne sert qu'une fois. L'identifiant du paiement ne change pas.

Les autres endpoints utilisent le verrou et le cache, mais pas l'index. Considérez 24 heures comme la période utile pour tous les endpoints et fiez-vous à vos propres enregistrements au-delà.

Les webhooks nécessitent leur propre déduplication

Les clés d'idempotence protègent les appels que vous effectuez. Elles ne protègent pas les appels de Wajub vers votre système, qui sont volontairement répétés cinq fois après 30 secondes, 1 minute, 5 minutes, 10 minutes et 1 heure. Si un handler répond lentement ou si un déploiement arrive au mauvais moment, vous recevrez deux fois le même événement.

L'identifiant de l'événement reste identique lors de chaque nouvelle tentative de livraison. Enregistrez-le dans une table avant d'agir, plutôt qu'en mémoire. Un Set interne au processus se vide lors du déploiement qui a justement créé le retard que vous allez recevoir.

Une ligne par événement, avec une décision lors de l'insertion
CREATE TABLE processed_events (
  event_id     TEXT PRIMARY KEY,
  processed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- Returns a row the first time, nothing on every replay.
INSERT INTO processed_events (event_id)
VALUES ($1)
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;

Répondez à la livraison avec 200 dans les deux cas. Une répétition déjà traitée n'est pas une erreur. Toute autre réponse demande à Wajub de la renvoyer.

Que pensez-vous de ce contenu ?