Demo, all content is generated
Question

Customer got charged twice and has 2 subscriptions in Stripe

Solved · 673 views · asked by dirk_vl · edited

A customer emailed that they were charged twice. In Stripe I see two subscriptions for the same customer created 4 seconds apart. My DB also has two rows for her.

I think she double clicked the Upgrade button? How do I prevent this, and should I refund in Stripe or somewhere else?

What I’ve tried

Refunded one charge in Stripe and cancelled that subscription. Asked Claude to make it not happen again and it added a 500ms debounce on the button.

Comment

3 answers

Marked as helpful by the asker
tobiasw · edited

Debounce helps but isn't enough. Belt and braces:

  1. Before creating a checkout session, check if the customer already has an active/trialing subscription. If yes, send them to the customer portal instead.
  2. Reuse one Stripe customer per user. Store stripe_customer_id on your user and pass customer: to the session. Without it every checkout can create a new customer and you can't even detect duplicates.
  3. Disable the button while the request is running (real disabled state, not debounce).
  4. Make the webhook idempotent. Unique constraint on stripe_subscription_id and upsert. Also store processed event.ids:
    create table stripe_events (id text primary key, processed_at timestamptz default now());
    Insert first, and if it conflicts, skip. Stripe retries and can deliver an event more than once.

Refunding and cancelling in Stripe was right. Your webhook should then pick up customer.subscription.deleted and clean up the second row.

Comment
No 2 was it, we created a new customer every checkout. Fixed all four. dirk_vl · edited
marco_py · edited

For the checkout-session creation call you can also pass an idempotency key (e.g. checkout-${userId}-${priceId}-${minute}) so two identical requests within a short window return the same session.

Comment
didn't know checkout sessions accept idempotency keys, nice dirk_vl · edited
ingrid_h · edited

Checkout settings also has an option to limit customers to one subscription. If they already have one, Stripe sends them to manage it instead of buying again. Needs the customer passed in, so fix #2 first.

Comment