Diagnostic guide
refunded customer still has access
Refunding a payment is a Stripe action. Ending access is a decision you have to make and code separately: the two are not the same event, and Stripe has no way to know which one you want without being told.
most likely causes, in order
01charge.refunded is not handled at all+
how it looks
Every refund you have ever issued, from the dashboard or through support, has left access untouched. Nobody notices, because a refund reads as a resolution, not a new problem.
how to check
Search the webhook handler for charge.refunded. If it is absent, nothing downstream of the refund button runs.
the fix
Add the event. Decide the policy first (below), then write it.
02No policy was ever decided, so nothing was written+
how it looks
There is no bug to find here: charge.refunded is unhandled on purpose, or by omission, and nobody has decided whether it should revoke access.
how to check
This is a decision, not a check: does a refund on this product mean the customer should lose access? A partial goodwill refund usually should not. A full refund on request, or a fraud refund, usually should.
the fix
Pick the rule. If refunds should end access, handle the event. If they should not, this is expected behavior; mark it as such rather than treating it as a bug.
03The refund is issued from the Stripe dashboard, not the app+
how it looks
Refunds you process yourself, by hand, in Stripe never show up in your own logs, because nothing in your app triggered them.
how to check
A manual refund in the dashboard fires the same charge.refunded event as one issued through the API. If access still is not revoked, the webhook is the problem, not the refund path.
the fix
Same fix as above: handle the event. It does not matter how the refund was created.
04A partial refund is treated the same as a full one, or the reverse+
how it looks
A customer who received a small partial credit lost all access instantly, or a customer who got a full refund kept everything.
how to check
The event includes amount_refunded alongside the charge's original amount. A handler that revokes access on any charge.refunded, regardless of size, cannot tell the two apart.
the fix
Compare amount_refunded to the charge's amount. Revoke only when they are equal, unless your policy says otherwise.
what this does not tell you
None of this shows up by querying your own database: a refund does not change any column here by default, and that is exactly the problem. The only way to find who was refunded and still has access is to compare against Stripe directly, customer by customer.
Read-only, both sides. Three findings free, no account.
related
Canceled subscription still has access
Stripe shows canceled. Your app still grants access. The four ways a cancellation fails to reach your database, and how to close it for good.
Stripe dispute or chargeback, still has access
A dispute pulls the money back immediately and gives you roughly 7 to 21 days to respond. What has to be wired up so it also affects access, separate from answering the dispute itself.