From 30cf46370cd41a137bfc04158755bf3c2749f016 Mon Sep 17 00:00:00 2001 From: marcusDenslow Date: Fri, 22 May 2026 21:21:02 +0200 Subject: [PATCH] fix(cards): duplicate card entries after adding card through stripe webhook fixed --- api/handlers/webhook.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/api/handlers/webhook.go b/api/handlers/webhook.go index 84cb5e0..69a6dc7 100644 --- a/api/handlers/webhook.go +++ b/api/handlers/webhook.go @@ -222,12 +222,36 @@ func (h *WebhookHandler) handleCheckoutSessionCompleted(ctx context.Context, eve return } - // Idempotency: skip if this payment method is already saved. + // Idempotency: skip if this exact PaymentMethod is already in use var existing models.Card if err := db.Where("user_id = ? AND stripe_payment_id = ?", user.ID, pm.ID).First(&existing).Error; err == nil { return } + // Fingerprint deduplication. if the customer already has another card + // with the same fingerprint (same underlying card re-tokenized), detatch + // the new pm and skip the local insert. Mirrors SaveCards check. + if pm.Card != nil && pm.Card.Fingerprint != "" { + listParams := &stripe.PaymentMethodListParams{ + Customer: stripe.String(user.StripeCustomerID), + Type: stripe.String("card"), + } + iter := paymentmethod.List(listParams) + for iter.Next() { + other := iter.PaymentMethod() + if other.ID == pm.ID || other.Card == nil { + continue + } + if other.Card.Fingerprint == pm.Card.Fingerprint { + if _, err := paymentmethod.Detach(pm.ID, nil); err != nil { + webhookLog().Warn("failed to detach duplicate pm", "user_id", user.ID, "error", err) + } + webhookLog().Info("skipped duplicate card via fingerprint", "user_id", user.ID, "fingerprint", pm.Card.Fingerprint) + return + } + } + } + card := models.Card{ UserID: user.ID, StripePaymentID: pm.ID,