Don't compute it yourself, Stripe tracks the period.
Instead of cancelling, schedule it:
await stripe.subscriptions.update(subId, { cancel_at_period_end: true });The subscription stays active until the period ends, then becomes canceled and you get customer.subscription.deleted. In between, cancel_at_period_end is true, so you can show "Your plan ends on …" and a "Resume" button (set it back to false).
Access check stays simple: active/trialing = access. Your webhook updates status when it actually ends.
Or skip your own button and send users to the customer portal, which does exactly this by default.