fix(billing): handle sibling Stripe subs on cancel + gate Welcome page on real status (BUNYIP-225) #254
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/bunyip!254
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/BUNYIP-225-hardening-resub-cancel-race"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Two hardening fixes for the symptoms surfaced when staging webhook signature mismatch dropped every Stripe delivery for hours. Root cause was config (signing secret mismatch) and fixed by operator; these changes close the residual correctness gaps that would have made the same shape harder to diagnose next time and would have produced wrong state even on a healthy webhook.
handle_subscription_deleted now queries Stripe for any OTHER active or trialing subscription on the same customer before flipping the user to Canceled. A user can hold multiple Stripe subscriptions (a cancel-with-period-end racing a new re-sub, or the multi-sub edge case from the BUNYIP-225 repro). Without this, the OLD subscription's deferred deletion at period_end would silently cancel a re-subscribed user even though their newer sub is healthy. New StripeService::has_other_active_subscription lists up to 100 subs and filters on status in {active, trialing} AND id != deleted. A Stripe API failure during this check returns Err so the webhook claim is released and Stripe retries cleanly (BUNYIP-210 makes that safe), rather than flipping to Canceled on partial data.
checkout_success now reads /v1/memberships/me and gates the "Welcome aboard / membership is active" copy on subscription_status == Active. When status has not yet flipped (webhook delay, or a future signature mismatch hiding the failure), the page renders a "Finalizing your subscription..." card with a 3-second auto-refresh until the webhook lands. Lifetime members and already-Active users see the celebration unchanged. The previous shape only read user.subscription_tier (the tier string, set at signup) so it confidently rendered success even with subscription_status = Canceled - the exact lying-success-page surfaced in the BUNYIP-225 repro.
#BUNYIP-225
Two hardening fixes for the symptoms surfaced when staging webhook signature mismatch dropped every Stripe delivery for hours. Root cause was config (signing secret mismatch) and fixed by operator; these changes close the residual correctness gaps that would have made the same shape harder to diagnose next time and would have produced wrong state even on a healthy webhook. handle_subscription_deleted now queries Stripe for any OTHER active or trialing subscription on the same customer before flipping the user to Canceled. A user can hold multiple Stripe subscriptions (a cancel-with-period-end racing a new re-sub, or the multi-sub edge case from the BUNYIP-225 repro). Without this, the OLD subscription's deferred deletion at period_end would silently cancel a re-subscribed user even though their newer sub is healthy. New StripeService::has_other_active_subscription lists up to 100 subs and filters on status in {active, trialing} AND id != deleted. A Stripe API failure during this check returns Err so the webhook claim is released and Stripe retries cleanly (BUNYIP-210 makes that safe), rather than flipping to Canceled on partial data. checkout_success now reads /v1/memberships/me and gates the "Welcome aboard / membership is active" copy on subscription_status == Active. When status has not yet flipped (webhook delay, or a future signature mismatch hiding the failure), the page renders a "Finalizing your subscription..." card with a 3-second auto-refresh until the webhook lands. Lifetime members and already-Active users see the celebration unchanged. The previous shape only read user.subscription_tier (the tier string, set at signup) so it confidently rendered success even with subscription_status = Canceled - the exact lying-success-page surfaced in the BUNYIP-225 repro. #BUNYIP-225