Aller au contenu

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

  1. Dans le dashboard Checkout.com, ouvrir la transaction à rembourser (celle dont le paymentId n'est PAS dans la WT)
  2. Cliquer sur le bouton "Refund"
  3. 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

SELECT c."withdrawableBalances", c."giftBalances"
FROM customers c
WHERE c.id = '{CUSTOMER_ID}'

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