DEV Community

shaojie gong
shaojie gong

Posted on

I spent a day building the button that lets people cancel and leave me

I spent a full day building the one button that makes it easy for people to stop paying me. On purpose.

Here's why that's not as dumb as it sounds.

NotebookBloom, my NotebookLM extension, has a Pro tier. Payment goes through a Stripe Payment Link — you click, you pay, you're Pro. Clean. Done in an afternoon. And for a while I thought that was the whole billing story.

It isn't. A Payment Link is a one-way door. It's great at letting money in. It does nothing about letting people out. There was no cancel screen anywhere in my product. If you subscribed and then wanted to stop, your options were: email me and wait, or... call your bank.

That second option is the one that should scare every solo founder.

When a customer can't find how to cancel, they don't shrug and keep paying. They dispute the charge — a chargeback. And chargebacks don't just cost you that one payment plus a fee. Stripe watches your chargeback rate like a hawk. Cross a threshold and they can freeze or close your account. One angry user who couldn't find a cancel button is an annoyance. A pattern of them is an existential threat to the thing collecting all your revenue.

So the cancel button isn't a courtesy to users. It's insurance on my own business.

The clean way to do it is Stripe's Customer Portal — a hosted page where a customer can cancel, change their card, or download invoices, none of which I have to build. I just have to send the right customer to the right portal session.

That "right customer" part is where the actual work was. The portal link has to be generated per-customer, server-side, and I had to make sure user A can't open user B's billing portal. My flow: the extension sends the user's Google token to my Cloudflare Worker. The worker verifies that token with Google to get the real email — the client never just says "I'm bob@gmail" and gets believed, because that's how you'd let anyone cancel anyone's subscription. With the verified email, the worker looks up the Stripe customer id, asks Stripe for a one-time portal session, and hands the URL back. The extension opens it in a new tab. One "Manage subscription" button in settings, and everything else is Stripe's problem.

The loop closes on its own: user cancels in the portal, Stripe fires a subscription.deleted webhook, my worker flips their record to inactive, and the next time the extension revalidates, they're back to free. I don't touch anything.

There was one detail I got wrong at first, mentally. I assumed "cancel" meant "Pro turns off now." It doesn't — Stripe defaults to canceling at the end of the paid period. Which is correct! They paid for the month, they get the month. So a canceled-but-still-active subscription is a real state I had to handle: the extension now shows "Pro access until [date] — won't renew" instead of just yanking it. Small thing, but it's the difference between feeling respected and feeling robbed.

The reframe I'm keeping: the exit is part of the product. I spent the first afternoon on the part that takes money and thought I was done. The part that lets people leave gracefully turned out to matter more — to my Stripe account's health, and to whether anyone trusts me enough to subscribe in the first place.

Did anyone else find out the hard way that "accept payments" and "handle subscriptions" are two completely different amounts of work?

— building NotebookBloom in public, #14

Top comments (1)

Collapse
 
topstar_ai profile image
Luis Cruz

I appreciate how you highlighted the importance of implementing a seamless cancellation process, which is often overlooked when building payment systems. The use of Stripe's Customer Portal is a great solution, and I'm impressed by the effort you put into ensuring the security of the portal link generation process. One thing that caught my attention was the distinction between "cancel" and "immediate Pro access removal" - it's a subtle but crucial detail that can greatly impact the user experience. Have you considered adding any notifications or follow-up emails to users after they cancel their subscription to gather feedback and improve the overall experience?