Diagnostic double débit Checkout.com¶
Workflow pour identifier un double paiement Checkout.com et fournir le checkoutPaymentId à rembourser manuellement dans le dashboard Checkout.
Contexte technique¶
Le flow Checkout crée une session avec un reference = futureWalletTransactionId. Si deux paiements sont capturés sur la même session :
- Le webhook payment_approved reçoit deux appels avec des checkoutPaymentId différents
- Le code détecte le doublon et alerte Slack sur le canal #checkout-double-paiement avec un lien cliquable vers le paiement à rembourser
- Le contexte WT conserve le dernier checkoutPaymentId reçu (keptPaymentId) ; l'alerte pointe vers le premier (paymentIdToRefund)
Le topup_card en declined est la tentative initiale via Lemonway qui a échoué, déclenchant le fallback vers Checkout.
Doc API : checkout.md (section double paiement).
Étape 1 : Identifier le client¶
SELECT c.id, c.email, cp."firstName", cp."lastName"
FROM customers c
LEFT JOIN customer_profile cp ON cp."customerId" = c.id
WHERE c.email = '{EMAIL}'
Ou directement par wallet ID (= customer ID dans le système Bricks) :
SELECT c.id, c.email, cp."firstName", cp."lastName"
FROM customers c
LEFT JOIN customer_profile cp ON cp."customerId" = c.id
WHERE c.id = '{WALLET_ID}'
Étape 2 : Trouver les wallet transactions autour de la date du double débit¶
SELECT wt.id, wt.kind, wt.value, wt.status, wt."createdAt",
wt.context,
wt."lemonwayTransactionId"
FROM wallet_transactions wt
WHERE wt."customerId" = '{CUSTOMER_ID}'
AND wt."createdAt" >= '{DATE_DEBUT}'
AND wt."createdAt" < '{DATE_FIN}'
AND wt.kind IN ('topup_checkout', 'topup_card')
ORDER BY wt."createdAt" ASC
Chercher le pattern suivant :
- Un topup_card en declined (tentative Lemonway échouée)
- Un topup_checkout en confirmed (paiement Checkout réussi)
- Même montant, timestamps proches (quelques minutes)
Étape 3 : Extraire le checkoutPaymentId¶
Le checkoutPaymentId dans le context de la WT topup_checkout est celui qui a été conservé (last-write-wins). C'est le paiement valide côté Bricks.
SELECT wt.id as wallet_transaction_id,
wt.value as amount_cents,
wt.status,
wt.context->>'checkoutPaymentId' as checkout_payment_id_conserve,
wt.context->>'checkoutReportId' as checkout_report_id,
wt.context->'card'->>'maskedNumber' as card_last4,
wt.context->'card'->>'brand' as card_brand
FROM wallet_transactions wt
WHERE wt."customerId" = '{CUSTOMER_ID}'
AND wt.kind = 'topup_checkout'
AND wt."createdAt" >= '{DATE_DEBUT}'
AND wt."createdAt" < '{DATE_FIN}'
ORDER BY wt."createdAt" ASC
Étape 4 : Identifier le paiement à rembourser¶
Priorité : utiliser l'alerte Slack #checkout-double-paiement si elle existe — elle contient déjà le paymentIdToRefund avec lien dashboard Checkout et le keptPaymentId conservé sur la WT.
Sinon, investigation manuelle :
1. Aller dans le dashboard Checkout.com → "All payments"
2. Chercher par email du client
3. Deux transactions "Captured" apparaissent pour le même montant/même carte/même timestamp
4. Le checkoutPaymentId conservé dans la WT (étape 3) est le paiement valide
5. L'autre checkoutPaymentId (visible dans Checkout mais absent de la DB) est celui à rembourser
Alternativement, vérifier dans les logs Datadog (si disponible) le log du webhook checkoutWebhookPaymentSuccess pour retrouver les deux data.id reçus.
Étape 5 : Procédure de remboursement¶
- Dans le dashboard Checkout.com, ouvrir la transaction à rembourser (celle dont le paymentId n'est PAS dans la WT)
- Cliquer sur le bouton "Refund"
- En commentaire indiquer :
"paiement en doublon avec la même session de paiement, cf ticket #{TICKET_ID}"
Droits requis : admin Checkout.com (contacter Vincent Besnier ou Denis Mludek pour les accès).
Étape 6 : Vérifications complémentaires¶
Confirmer que le client n'a pas été crédité deux fois côté Bricks¶
SELECT wt.id, wt.kind, wt.value, wt.status, wt."createdAt"
FROM wallet_transactions wt
WHERE wt."customerId" = '{CUSTOMER_ID}'
AND wt.value > 0
AND wt."createdAt" >= '{DATE_DEBUT}'
AND wt."createdAt" < '{DATE_FIN}'
ORDER BY wt."createdAt" ASC
Le client ne doit avoir qu'un seul crédit (topup_checkout confirmed) pour le montant en question. Si deux crédits existent, escalader.
Vérifier le solde actuel¶
Résumé du diagnostic à fournir¶
Fournir au support : 1. checkoutPaymentId conservé (dans la WT) = paiement valide, ne pas toucher 2. checkoutPaymentId à rembourser = celui visible dans Checkout.com mais absent de la DB 3. Confirmation qu'un seul crédit wallet existe côté Bricks (pas de double crédit) 4. Instructions pour le remboursement dans le dashboard Checkout.com