Add admin Manage Subscriptions page (view/pause/resume/cancel Stripe subscriptions) - #1443
Merged
Merged
Conversation
- Create ISubscriptionHandlerService interface with ListBillableAsync method - Implement StripeSubscriptionHandlerService with pagination and customer expansion - Filter subscriptions to only billable statuses (active, past_due, trialing, unpaid) - Exclude canceled subscriptions from results - Register service in DI container - Add comprehensive unit tests covering filtering and expansion behavior Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements CancelAtPeriodEndAsync and CancelImmediatelyAsync on StripeSubscriptionHandlerService. CancelAtPeriodEndAsync sets CancelAtPeriodEnd=true to defer cancellation until the subscription's current billing period ends. CancelImmediatelyAsync calls Stripe's cancel endpoint to immediately terminate the subscription. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement web-layer DTO and AutoMapper profile for mapping Stripe.Subscription objects to StripeSubscriptionDto. The profile handles nested property extraction (customer email, plan details, pricing) and null-conditional chaining for optional fields. Stripe amounts in cents are converted to decimal dollars via division by 100. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Strengthen test to verify subscriptions are ordered by CustomerEmail then Id, and to assert mapped fields end-to-end. Test now provides subscriptions in non-sorted order with same-email pair to exercise ThenBy tie-break logic. Would fail if OrderBy/ThenBy calls were removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement POST handlers for subscription actions (pause, resume, cancel at period end, cancel immediately) on the Manage Subscriptions admin page. Add action buttons to the UI with confirmation dialogs. Add the Manage Subscriptions link to the admin menu. Includes test class for handlers (discovery issue pending). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Remove member-controlled data (CustomerEmail, subscription Id) from
onsubmit confirm() JS strings in ManageSubscriptions/Index.cshtml.
Razor HTML-encoding is decoded by the HTML parser before the JS
engine sees it, so a literal apostrophe (e.g. o'brien@example.com)
reached the JS string context, silently skipping confirmation
(including for irreversible Cancel Now) or enabling stored XSS in
the admin session. Confirm messages are now static.
- Fix CustomerSubscriptionUpdatedWebHook comparing stripeEvent.Type
against EventTypes.CustomerUpdated ("customer.updated") instead of
EventTypes.CustomerSubscriptionUpdated ("customer.subscription.updated"),
which made the endpoint reject every real event it receives with an
uncaught 500.
- Harden StripeSubscriptionHandlerService.ListBillableAsync's pagination
loop against an infinite loop if Stripe ever returns HasMore=true with
an empty Data page.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an admin-only Manage Subscriptions page at
/Admin/ManageSubscriptions(linked from the Admin menu) that works directly against the Stripe API:active,past_due,trialing,unpaid) with customer email, plan, amount, interval, status badges (Paused / Cancels-at-period-end), and current period end. Pagination correctly advancesStartingAfter(unlike the existing invoice list service, whose loop never advances the cursor).pause_collectionwithbehavior: "void"(Stripe stops charging and voids invoices generated while paused; membership access is unaffected since nocustomer.subscription.deletedevent fires).Implementation
ISubscriptionHandlerService/StripeSubscriptionHandlerServicein Infrastructure (mirrors theIInvoiceHandlerListServicehandler pattern), registered in DI.StripeSubscriptionDto+SubscriptionProfile(AutoMapper) in Web, auto-registered via the existingAddMapsscan.Pause,Resume,Cancel,CancelNow),[TempData]status messages,StripeExceptionhandling, blank-ID guards, and confirm dialogs (static strings — no user data interpolated into JS).Also fixes a pre-existing webhook bug
CustomerSubscriptionUpdatedWebHookcompared the event type againstEventTypes.CustomerUpdated("customer.updated") instead ofEventTypes.CustomerSubscriptionUpdated, so every realcustomer.subscription.updatedevent threw and returned 500 to Stripe — breaking the future-cancellation email flow. Fixed here because pausing/cancelling emits exactly this event.Testing
Follow-ups (documented, out of scope)
Amountshows first item price only (ignores quantity/multi-item subscriptions).OnGetAsynchas no friendly error page on Stripe outage.status=all) then filters in memory; per-status queries would bound it as churn grows.🤖 Generated with Claude Code