feat: Next generation Stripe billing integration#2161
feat: Next generation Stripe billing integration#2161
Conversation
- Upgrade Stripe.net to v50.4.1 and update the backend to support modern PaymentMethods while maintaining legacy token compatibility. - Implement a new billing feature in the Svelte UI with lazy-loaded Stripe integration and a functional plan change dialog. - Add TanStack Query hooks for fetching available plans and processing plan changes with coupon support.
985add7 to
ee79cb9
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces a “next generation” Stripe billing integration spanning backend Stripe.net v50 updates, new billing endpoints/DTO mapping, and a new Svelte 5 billing UI (Stripe provider + change-plan dialog) wired into organization/project usage and billing pages.
Changes:
- Upgrades Stripe.net usage on the backend (invoice status handling, discounts, subscription updates, PaymentMethod support).
- Adds a new Svelte billing feature module (
StripeProvider,ChangePlanDialog) and hooks it into usage/billing routes. - Extends org client API with plan query + change-plan mutation; updates invoice mapping/tests accordingly.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Exceptionless.Tests/Mapping/InvoiceMapperTests.cs | Updates Stripe invoice test data to use Status instead of Paid. |
| tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs | Simplifies test comments (no functional change). |
| src/Exceptionless.Web/Mapping/InvoiceMapper.cs | Maps “paid” via Invoice.Status string comparison. |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Updates Stripe invoice retrieval/discount handling and modernizes change-plan flow to support PaymentMethods + Stripe.net 50 API changes. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte | “Change plan” now navigates to the org billing page. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte | “Change plan” now navigates to the org billing page. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/billing/+page.svelte | Uses new billing module ChangePlanDialog and passes loaded org data into it. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/change-plan-dialog.svelte | Removes the legacy placeholder change-plan dialog component. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds getPlansQuery() and changePlanMutation() + related query keys/types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/stripe.svelte.ts | Adds Stripe context + lazy singleton loader utilities. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/schemas.ts | Adds zod schema/types for the change-plan form. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/models.ts | Adds billing model re-exports + local billing form/params types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/index.ts | Barrel exports for billing feature module. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/constants.ts | Defines FREE_PLAN_ID. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/stripe-provider.svelte | Adds Stripe Elements provider wrapper with loading/error states. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.svelte | Implements the plan change dialog UI including Stripe payment collection. |
| src/Exceptionless.Web/ClientApp/package.json | Adds Stripe JS + svelte-stripe dependencies. |
| src/Exceptionless.Web/ClientApp/package-lock.json | Locks Stripe JS + svelte-stripe dependencies. |
| src/Exceptionless.Web/ClientApp/STRIPE-INTEGRATION-PLAN.md | Adds the Stripe integration plan document. |
| src/Exceptionless.Core/Exceptionless.Core.csproj | Upgrades Stripe.net to 50.4.1. |
| .claude/agents/engineer.md | Adds guidance for rerunning flaky CI jobs via gh run rerun. |
Files not reviewed (1)
- src/Exceptionless.Web/ClientApp/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
This PR introduces a “next generation” Stripe billing integration across the backend (Stripe.net 50.x) and the Svelte client, enabling plan selection/changes and invoice display with the updated Stripe API surface.
Changes:
- Upgrades Stripe.net to 50.4.1 and updates invoice/plan-change logic to use
Status,Price,Discounts, andPaymentMethodAPIs. - Adds a new client-side billing feature module (Stripe context/provider + change plan dialog) and wires it into usage/billing pages.
- Adds client API helpers for fetching plans and performing plan changes, with cache invalidation.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Exceptionless.Tests/Mapping/InvoiceMapperTests.cs | Updates invoice mapping tests to reflect Stripe Status replacing Paid. |
| tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs | Simplifies test comments (no functional change). |
| src/Exceptionless.Web/Mapping/InvoiceMapper.cs | Switches paid logic to derive from Invoice.Status. |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Updates invoice retrieval and plan change flows for Stripe.net 50.x (Price/Discounts/PaymentMethod changes). |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte | Replaces placeholder “change plan” handler with navigation to billing page. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte | Same as above for org usage page. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/billing/+page.svelte | Switches to the new billing module’s ChangePlanDialog and passes organization data. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/change-plan-dialog.svelte | Removes the old placeholder change-plan dialog component. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds getPlansQuery + changePlanMutation and associated query keys/types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/stripe.svelte.ts | Adds Stripe context utilities and a singleton Stripe loader. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/schemas.ts | Adds Zod schema for the change-plan form. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/models.ts | Adds billing-focused types and re-exports generated API models. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/index.ts | Exposes billing module public API (components/hooks/constants/types). |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/constants.ts | Introduces FREE_PLAN_ID. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/stripe-provider.svelte | Adds a Stripe <Elements> provider and sets Stripe context for children. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.svelte | Implements the new plan change UX using Stripe PaymentElement + plans query + mutation. |
| src/Exceptionless.Web/ClientApp/package.json | Adds @stripe/stripe-js and svelte-stripe. |
| src/Exceptionless.Web/ClientApp/package-lock.json | Locks new Stripe dependencies. |
| src/Exceptionless.Core/Exceptionless.Core.csproj | Upgrades Stripe.net from 47.4.0 to 50.4.1. |
Files not reviewed (1)
- src/Exceptionless.Web/ClientApp/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new Stripe-based billing UX in the Svelte app and updates the backend Stripe integration to newer Stripe.net APIs, alongside some infra/telemetry adjustments.
Changes:
- Adds new billing feature module on the frontend (Stripe provider + change-plan dialog) and wires it into usage/billing pages.
- Updates backend invoice/payment handling to Stripe.net 50.x patterns (invoice status, discounts, line item pricing/price lookup).
- Updates dependencies/config (Stripe.net upgrade, adds
@stripe/stripe-js+svelte-stripe, adjusts OpenTelemetry Prometheus wiring and deploy workflow conditions).
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Exceptionless.Tests/Mapping/InvoiceMapperTests.cs | Updates invoice mapping tests to use Status instead of Paid. |
| tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs | Simplifies test comments (no functional change). |
| src/Exceptionless.Web/Startup.cs | Removes Prometheus scraping endpoint middleware. |
| src/Exceptionless.Web/Mapping/InvoiceMapper.cs | Maps Paid from Invoice.Status == "paid". |
| src/Exceptionless.Web/Exceptionless.Web.csproj | Removes Prometheus exporter package reference; formatting changes. |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Updates invoice and plan-change flows for Stripe.net 50.x (prices/discounts/payment methods). |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte | Navigates to org billing page with changePlan=true. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte | Navigates to org billing page with changePlan=true. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/billing/+page.svelte | Switches to new billing ChangePlanDialog and passes organization data. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/change-plan-dialog.svelte | Deletes placeholder dialog (replaced by billing feature). |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds plans query + change-plan mutation/query keys. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/stripe.svelte.ts | Adds Stripe context + lazy loader utilities. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/schemas.ts | Adds Zod schema for change-plan form. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/models.ts | Adds billing types and form state types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/index.ts | Adds billing feature barrel exports. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/constants.ts | Adds FREE_PLAN_ID constant. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/stripe-provider.svelte | Adds Stripe Elements provider component. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.svelte | Adds new Stripe-backed change-plan dialog UI. |
| src/Exceptionless.Web/ClientApp/package.json | Adds Stripe JS dependencies. |
| src/Exceptionless.Web/ClientApp/package-lock.json | Locks Stripe JS dependencies. |
| src/Exceptionless.Web/ApmExtensions.cs | Removes Prometheus exporter from OpenTelemetry metrics pipeline. |
| src/Exceptionless.Job/Program.cs | Removes Prometheus scraping endpoint middleware from job host. |
| src/Exceptionless.Job/Exceptionless.Job.csproj | Removes Prometheus exporter package reference; formatting changes. |
| src/Exceptionless.Core/Exceptionless.Core.csproj | Upgrades Stripe.net from 47.4.0 to 50.4.1; formatting changes. |
| .vscode/settings.json | Updates workspace editor/TypeScript SDK settings. |
| .github/workflows/build.yaml | Updates deploy job condition to allow dev deploys from a configured PR branch. |
Files not reviewed (1)
- src/Exceptionless.Web/ClientApp/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
This PR introduces a next-generation Stripe-based billing flow across the Svelte app and the organization API, updating both frontend plan-change UX and backend Stripe integration to newer Stripe.net APIs.
Changes:
- Adds a new Billing feature module (Stripe context/provider + ChangePlanDialog) and wires it into the organization billing page and usage pages.
- Updates backend Stripe integration (Stripe.net 50.x) for invoice/payment/plan handling and updates invoice mapping/tests accordingly.
- Updates build/ops tooling (Docker MinVer build arg, workflow tweaks) and removes Prometheus OpenTelemetry exporter/scraping.
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Exceptionless.Tests/Mapping/InvoiceMapperTests.cs | Updates tests to reflect invoice Status -> paid mapping. |
| tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs | Simplifies test comments (no behavior change). |
| src/Exceptionless.Web/Startup.cs | Removes OTEL Prometheus scraping endpoint middleware. |
| src/Exceptionless.Web/Mapping/InvoiceMapper.cs | Maps invoice “paid” from Stripe Status instead of Paid. |
| src/Exceptionless.Web/Exceptionless.Web.csproj | Removes Prometheus exporter package + formatting changes. |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Updates Stripe invoice/plan change logic for Stripe.net 50.x and PaymentMethod support. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte | Implements navigation to billing page on “Change plan”. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte | Implements navigation to billing page on “Change plan”. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/billing/+page.svelte | Switches to new billing ChangePlanDialog component and passes organization data. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/change-plan-dialog.svelte | Removes placeholder dialog from organizations feature. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds plans query + change-plan mutation and query keys. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/stripe.svelte.ts | Adds Stripe lazy-loader + context hooks. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/schemas.ts | Adds Zod schema for change-plan form. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/models.ts | Adds billing feature types and re-exports generated API types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/index.ts | Public entrypoint for billing feature exports. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/constants.ts | Adds FREE plan id constant. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/stripe-provider.svelte | Adds provider wrapping Stripe Elements + loading/error UI. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.svelte | Adds full Stripe Elements-based plan change dialog. |
| src/Exceptionless.Web/ClientApp/package.json | Adds Stripe JS dependencies. |
| src/Exceptionless.Web/ClientApp/package-lock.json | Locks Stripe JS dependencies. |
| src/Exceptionless.Web/ApmExtensions.cs | Removes Prometheus exporter wiring. |
| src/Exceptionless.Job/Program.cs | Removes OTEL Prometheus scraping endpoint middleware. |
| src/Exceptionless.Job/Exceptionless.Job.csproj | Removes Prometheus exporter package + formatting changes. |
| src/Exceptionless.Core/Exceptionless.Core.csproj | Upgrades Stripe.net to 50.4.1. |
| Dockerfile | Adds MinVerVersionOverride build arg passthrough. |
| .vscode/settings.json | Disables format-on-save and changes TypeScript SDK path setting key. |
| .github/workflows/build.yaml | Ensures checkout uses correct ref and passes MinVer build arg into Docker builds; expands deploy conditions. |
Files not reviewed (1)
- src/Exceptionless.Web/ClientApp/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Stripe v9 requires either clientSecret OR mode when calling stripe.elements(). The previous code passed neither when no clientSecret was provided, causing PaymentElement to fail to render. Uses a discriminated union Props type to enforce mutual exclusivity of clientSecret and mode at compile time, matching Stripe's own type constraints. Defaults to mode='setup' for collecting payment methods for future use.
Fix two issues preventing the Stripe PaymentElement from rendering in the Change Plan dialog: 1. Missing currency in Stripe Elements options: Stripe.js v9+ requires a currency string when using mode: 'setup'. Added currency: 'usd' to the elements creation options. 2. svelte-stripe Svelte 5 incompatibility: svelte-stripe's <Elements> and <PaymentElement> components use $bindable()/onMount patterns that don't trigger Svelte 5 template re-renders from async callbacks. Replaced both components with an imperative approach that loads Stripe, creates elements, and mounts the PaymentElement directly to the DOM via onMount, bypassing Svelte's reactive template system entirely. Additionally fixed the Change Plan dialog not being scrollable by adding max-h-[90vh] and overflow-y-auto to the Dialog.Content wrapper. Changes: - stripe-provider.svelte: Rewritten to imperatively mount PaymentElement without svelte-stripe components or Svelte reactive conditionals - change-plan-dialog.svelte: Removed svelte-stripe PaymentElement import, updated StripeProvider usage (no longer takes children), added dialog scroll constraints, removed debug markup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Automatically scroll the coupon section into view and focus the input when opened or when an error occurs. - Scroll to the payment section when the user chooses to use a different card. - Use smooth scrolling to ensure context is maintained during navigation within the dialog.
There was a problem hiding this comment.
Pull request overview
This PR introduces a next-generation Stripe billing integration across the ASP.NET Core API and the Svelte 5 “next” UI, including a Stripe.net SDK upgrade and new billing UX flows (change plan, invoices, upgrade-required handling).
Changes:
- Upgrade backend Stripe integration to Stripe.net v51 and adjust invoice/plan-change logic accordingly.
- Add Svelte billing feature module (StripeProvider, ChangePlanDialog, UpgradeRequiredDialog) and wire it into the app.
- Update OpenAPI + generated client types/schemas and refresh related tests/http examples.
Reviewed changes
Copilot reviewed 40 out of 46 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/http/organizations.http | Updates example requests for change-plan (JSON body + legacy query). |
| tests/Exceptionless.Tests/Mapping/InvoiceMapperTests.cs | Updates invoice mapping tests to use Stripe Status instead of Paid. |
| tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs | Simplifies test comments (no behavior change). |
| tests/Exceptionless.Tests/Controllers/Data/openapi.json | Updates OpenAPI snapshot (server URL + change-plan request body + schema additions). |
| src/Exceptionless.Web/Program.cs | Adds support for optional appsettings.Local.yml. |
| src/Exceptionless.Web/Models/ChangePlanRequest.cs | Adds request model for JSON-body plan changes. |
| src/Exceptionless.Web/Mapping/InvoiceMapper.cs | Maps invoice “paid” state based on Stripe Status. |
| src/Exceptionless.Web/Exceptionless.Web.csproj | Formatting-only change. |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Implements JSON-body plan changes, Stripe.net v51 invoice updates, plan list behavior changes. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/add/+page.svelte | Adds upgrade-required handling during project creation errors. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte | Implements “Change plan” navigation. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/integrations/+page.svelte | Adds upgrade-required handling for integrations mutations. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/add/+page.svelte | Adds upgrade-required handling during org creation errors. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/users/+page.svelte | Adds upgrade-required handling when inviting users. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte | Implements “Change plan” navigation. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/billing/+page.svelte | Switches billing page to new ChangePlanDialog and dialog-open state handling. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/event/[eventId]/+page.svelte | Uses shared upgrade-required handling on 426. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte | Mounts UpgradeRequiredDialog globally. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts | Adds ChangePlanRequest schema; adjusts multiple generated schemas. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts | Adds ChangePlanRequest type; updates NewToken + PersistentEvent typings. |
| src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-options-dropdown-menu.svelte | Routes 426 “upgrade required” to shared handler. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/change-plan-dialog.svelte | Removes legacy placeholder change-plan dialog. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds change-plan mutation + plans query. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/upgrade-required.svelte.ts | Adds shared upgrade-required state + helpers. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/stripe.svelte.ts | Adds Stripe context helpers and singleton Stripe loader. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/schemas.ts | Adds UI form schema for change-plan dialog. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/models.ts | Adds billing model re-exports + UI types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/index.ts | Public barrel exports for billing feature module. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/constants.ts | Adds FREE_PLAN_ID constant. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/upgrade-required-dialog.svelte | Adds the upgrade-required confirmation dialog UI. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/stripe-provider.svelte | Adds imperative Stripe PaymentElement mounting/provider. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.svelte | Adds full plan change modal UI and Stripe payment method handling. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.stories.ts | Adds Storybook coverage for billing dialog states. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog-harness.svelte | Adds Storybook harness wiring for queries/auth. |
| src/Exceptionless.Web/ClientApp/src/app.css | Adjusts --primary-foreground theme variables. |
| src/Exceptionless.Web/ClientApp/package.json | Adds @stripe/stripe-js dependency. |
| src/Exceptionless.Web/ClientApp/package-lock.json | Locks @stripe/stripe-js dependency. |
| src/Exceptionless.Web/ClientApp/.storybook/mocks/env.js | Adds mock Stripe publishable key for Storybook. |
| src/Exceptionless.Web/ClientApp.angular/components/organization/organization-service.js | Updates legacy Angular change-plan call to send JSON body. |
| src/Exceptionless.Job/Exceptionless.Job.csproj | Formatting-only change. |
| src/Exceptionless.Core/Exceptionless.Core.csproj | Upgrades Stripe.net package to v51. |
| docs/billing-stripe-integration.md | Adds Stripe/billing integration documentation. |
| .vscode/settings.json | Updates VS Code TypeScript SDK setting name. |
| .vscode/launch.json | Modifies Aspire launch configuration. |
| .github/hooks/follow-up.json | Adds GitHub hook configuration. |
| .aspire/settings.json | Formatting-only change. |
Files not reviewed (1)
- src/Exceptionless.Web/ClientApp/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Test GetPlansAsync for correct plan visibility and organization-level value overlays across different user roles. - Verify that billing-related endpoints (change-plan, invoices) return NotFound when billing is disabled in the configuration.
- Remove validateFilter API call, RequiresPremiumFeaturesState global state, and the \ hacks that triggered it on dashboard/stream/issues pages - Add premium-filter.ts with filterUsesPremiumFeatures() — pure function using the same free-field whitelist as PersistentEventQueryValidator on the backend - Derive requiresPremium in +layout.svelte so OrganizationNotifications reacts to URL filter changes without an extra network round-trip or effect - Extract inline handleError/handleUpgrade callbacks to named functions across dashboard, stream, issues, and notifications pages - Fix void retry() → async/await in upgrade-required-dialog onChangePlanClose - Replace per-component \(!!env.PUBLIC_STRIPE_PUBLISHABLE_KEY) with isStripeEnabled() from the billing module - Extract onOpenChange inline callback to named handleOpenChange function - Fix notifications handleUpgrade to use showUpgradeDialog() dialog instead of navigating away to the billing page - Suppress 426 from TanStack Query retry logic in root layout
…premium state - Replace void retry() with async/await in upgrade-required-dialog onChangePlanClose - Convert inline filterUsesPremiumFeatures() call to dollar-sign-derived in +layout - Extract inline handleError/handleUpgrade callbacks to named functions on dashboard, stream, issues, and notifications pages - Use isStripeEnabled() from billing module instead of per-component env check - Extract onOpenChange inline arrow to named handleOpenChange function - Suppress 426 from TanStack Query retry in root layout - Add retry callbacks to all 426 handlers (add project/org, invite user, webhooks) - Add showUpgradeDialog() for proactive upgrade prompts without a 426 response - Widen user-notification-settings-form upgrade prop to accept void return
There was a problem hiding this comment.
Pull request overview
Introduces a next-generation Stripe billing flow across the ASP.NET Core API and the Svelte 5 client, including a new plan-change dialog (Stripe PaymentElement) and centralized “Upgrade required” (426) handling, while migrating backend Stripe usage to Stripe.net v51.
Changes:
- Added new billing feature module in the Svelte app (ChangePlanDialog, StripeProvider, UpgradeRequiredDialog) and wired 426 upgrade prompts across routes.
- Updated organization billing APIs (plans, change-plan, invoices/invoice) and invoice mapping for Stripe.net v51 (status/discount/price changes).
- Updated generated OpenAPI client artifacts, tests, and documentation for the new change-plan request body + legacy query compatibility.
Reviewed changes
Copilot reviewed 47 out of 53 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/http/organizations.http | Adds preferred JSON-body change-plan request + keeps legacy query example. |
| tests/Exceptionless.Tests/Mapping/InvoiceMapperTests.cs | Updates invoice “paid” expectation to use Stripe invoice Status. |
| tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs | Test cleanup/comment adjustments (Arrange/Act/Assert structure). |
| tests/Exceptionless.Tests/Controllers/OrganizationControllerTests.cs | Adds coverage for plans endpoint + change-plan/invoices behavior when billing disabled. |
| tests/Exceptionless.Tests/Controllers/Data/openapi.json | Documents change-plan JSON request body + marks query params as legacy. |
| src/Exceptionless.Web/Program.cs | Loads optional appsettings.Local.yml. |
| src/Exceptionless.Web/Models/ChangePlanRequest.cs | Adds request model for JSON-body change-plan payload. |
| src/Exceptionless.Web/Mapping/InvoiceMapper.cs | Maps “Paid” from Stripe Status == paid. |
| src/Exceptionless.Web/Exceptionless.Web.csproj | Formatting-only change. |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Implements JSON-body change-plan (with legacy query fallback) + Stripe.net v51 invoice/price/discount updates + plans overlay fix. |
| src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte | Avoids query retry for 426 (Upgrade Required). |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte | Handles 426 upgrade prompt and clears selection on event load errors. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/add/+page.svelte | Uses upgrade-required handler during project creation failures. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte | Navigates to billing dialog from project usage page. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/integrations/+page.svelte | Uses upgrade-required handler for webhook/slack flows. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/add/+page.svelte | Uses upgrade-required handler during organization creation failures. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/users/+page.svelte | Uses upgrade-required handler when inviting users. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte | Navigates to billing dialog from org usage page. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/billing/+page.svelte | Swaps legacy placeholder dialog for new billing ChangePlanDialog. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte | Centralizes 426 handling + clears selected stack on errors. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/event/[eventId]/+page.svelte | Centralizes 426 handling and redirects back to app root. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/account/notifications/+page.svelte | Adds proactive upgrade dialog for premium notifications. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte | Centralizes 426 handling for events list + clears selection on errors. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte | Mounts UpgradeRequiredDialog and adds premium-filter detection for notifications component. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts | Adds ChangePlanRequest schema + adjusts several generated schemas. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts | Adds ChangePlanRequest type + adjusts several generated types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-options-dropdown-menu.svelte | Replaces inline premium toast with upgrade-required handler. |
| src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/user-notification-settings-form.svelte | Allows upgrade handler callback to be sync or async. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/change-plan-dialog.svelte | Removes deprecated placeholder change-plan dialog. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds plans query + changePlan mutation and query keys. |
| src/Exceptionless.Web/ClientApp/src/lib/features/events/premium-filter.ts | Adds client-side detection of premium Lucene fields. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/upgrade-required.svelte.ts | Adds shared upgrade-required dialog state + helpers. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/stripe.svelte.ts | Adds Stripe lazy loader + Stripe context helpers. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/schemas.ts | Adds client form schema for change-plan dialog. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/models.ts | Adds billing-specific model exports/types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/index.ts | Exposes billing module public API (components/helpers). |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/constants.ts | Defines FREE_PLAN_ID constant. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/upgrade-required-dialog.svelte | Implements the 426 “Upgrade Plan” dialog + opens ChangePlanDialog. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/stripe-provider.svelte | Implements imperative Stripe Elements/PaymentElement mounting for Svelte 5. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.svelte | Adds the full plan selection + payment + coupon dialog UI. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.stories.ts | Adds Storybook stories for change-plan dialog states. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog-harness.svelte | Adds Storybook harness + query client mocking for plans. |
| src/Exceptionless.Web/ClientApp/src/app.css | Updates primary foreground color variables. |
| src/Exceptionless.Web/ClientApp/package.json | Adds @stripe/stripe-js dependency. |
| src/Exceptionless.Web/ClientApp/package-lock.json | Lockfile update for @stripe/stripe-js. |
| src/Exceptionless.Web/ClientApp/.storybook/mocks/env.js | Mocks Stripe publishable key for Storybook runs. |
| src/Exceptionless.Web/ClientApp.angular/components/organization/organization-service.js | Updates legacy Angular change-plan call to send JSON body. |
| src/Exceptionless.Job/Exceptionless.Job.csproj | Formatting-only change. |
| src/Exceptionless.Core/Exceptionless.Core.csproj | Upgrades Stripe.net package to 51.0.0. |
| docs/billing-stripe-integration.md | Adds design/architecture + migration notes doc for Stripe billing. |
| .vscode/settings.json | Updates TypeScript SDK setting key. |
| .github/hooks/follow-up.json | Adds a GitHub hook configuration (Stop hook). |
| .aspire/settings.json | Formatting-only change. |
Files not reviewed (1)
- src/Exceptionless.Web/ClientApp/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { | ||
| "hooks": { | ||
| "Stop": [ | ||
| { | ||
| "type": "command", | ||
| "command": "python3 -c \"\nimport json, sys\ndata = json.load(sys.stdin)\nif data.get('stop_hook_active', False):\n json.dump({'hookSpecificOutput': {'hookEventName': 'Stop'}}, sys.stdout)\nelse:\n json.dump({\n 'hookSpecificOutput': {\n 'hookEventName': 'Stop',\n 'decision': 'block',\n 'reason': 'Before stopping: 1) Summarize what was completed and what was not. 2) Ask the user if they have any follow-up questions, concerns, or additional changes. 3) If there are pending items from the original request that were not addressed, list them explicitly. Do NOT stop silently.'\n }\n }, sys.stdout)\n\"", | ||
| "timeout": 5 | ||
| } | ||
| ] | ||
| } | ||
| } |
| if (!_stripePromise) { | ||
| _stripePromise = loadStripe(env.PUBLIC_STRIPE_PUBLISHABLE_KEY!); | ||
| } | ||
|
|
||
| try { | ||
| _stripeInstance = await _stripePromise; | ||
| return _stripeInstance; | ||
| } catch (error: unknown) { | ||
| // Reset so the next call can retry instead of re-awaiting the rejected promise | ||
| _stripePromise = null; | ||
| _stripeInstance = null; | ||
| throw error; | ||
| } |
| | `planId` | string | Target plan ID (e.g., `EX_MEDIUM`, `EX_LARGE_YEARLY`) | | ||
| | `stripeToken` | string? | `pm_` PaymentMethod ID (Svelte) or `tok_` token (Angular) | | ||
| | `last4` | string? | Last 4 digits of card (display only) | | ||
| | `couponId` | string? | Stripe coupon code | |
| async function handleChangePlan() { | ||
| if (organization.current) { | ||
| await goto(`/next/organization/${organization.current}/billing?changePlan=true`); | ||
| } |
| async function handleChangePlan() { | ||
| await goto(`/next/organization/${page.params.organizationId}/billing?changePlan=true`); | ||
| } |
- Angular change-plan dialog: extract title/message from ProblemDetails error responses instead of showing generic error message - Narrow invoice GetAsync catch to StripeException (avoid swallowing unexpected exceptions) - Remove redundant ex.Message from structured log templates (already captured by the exception parameter) - Sort imports alphabetically (formatter)
Reviewed at |
- Unauthorized org access returns NotFound - Empty body with billing disabled returns NotFound - CanDownGradeAsync: too many users, too many projects, duplicate free plan, valid downgrade
Reviewed at |
- encodeURIComponent on invoiceId in Stripe URL to prevent injection - Add // Act & Assert markers to ChangePlanAsync tests for consistency
The C# 'required' keyword combined with .NET's RespectNullableAnnotations
caused System.Text.Json to reject valid JSON bodies with snake_case naming.
Keep [Required] attribute for model validation. Fixes POST /organizations/{id}/change-plan
JSON body deserialization.
- Cancel broken subscription with 0 items and create a new one instead of silently doing nothing - Replace raw StripeException.Message with generic error to avoid leaking Stripe internals to end users
- ChangePlanRequest.PlanId: use null! instead of String.Empty default
- Empty subscription items: update with new item instead of cancel+recreate
- Log StripeException.Message via structured {Message} template
- Keep sanitized user-facing error (no Stripe internals leak)
There was a problem hiding this comment.
Pull request overview
This PR introduces a “next generation” Stripe billing flow across the ASP.NET Core API and the Svelte 5 client, adding plan management UI (Stripe PaymentElement) and upgrade-required handling (HTTP 426) while updating supporting mappers, OpenAPI artifacts, and docs.
Changes:
- Adds new Svelte billing module (ChangePlanDialog, StripeProvider, upgrade-required dialog/state) and wires it into billing/usage flows.
- Updates
OrganizationControllerbilling endpoints (plans overlay, change-plan body + legacy query support, Stripe.net v51 migration fixes). - Updates generated OpenAPI client schemas/types and adds/updates tests + HTTP request samples.
Reviewed changes
Copilot reviewed 48 out of 54 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/http/organizations.http | Adds JSON-body “preferred” change-plan request and keeps legacy query example. |
| tests/Exceptionless.Tests/Mapping/InvoiceMapperTests.cs | Updates invoice “paid” mapping tests to Stripe status. |
| tests/Exceptionless.Tests/Controllers/TokenControllerTests.cs | Test comment/structure cleanups (Arrange/Act/Assert). |
| tests/Exceptionless.Tests/Controllers/OrganizationControllerTests.cs | Adds integration tests for /plans + billing-disabled behaviors and downgrade checks. |
| tests/Exceptionless.Tests/Controllers/Data/openapi.json | Updates OpenAPI description and adds ChangePlanRequest schema/requestBody. |
| src/Exceptionless.Web/Program.cs | Adds optional appsettings.Local.yml configuration source. |
| src/Exceptionless.Web/Models/ChangePlanRequest.cs | New API model for change-plan JSON body (snake_case via naming policy). |
| src/Exceptionless.Web/Mapping/InvoiceMapper.cs | Switches “paid” mapping to invoice.status == "paid". |
| src/Exceptionless.Web/Exceptionless.Web.csproj | Formatting-only change (EOF). |
| src/Exceptionless.Web/Controllers/OrganizationController.cs | Stripe.net v51 migration updates for invoices + change-plan supports JSON body + legacy query params; adds plans overlay behavior. |
| src/Exceptionless.Web/ClientApp/src/routes/+layout.svelte | Treats 426 as non-retriable in layout-level logic. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/stream/+page.svelte | Handles 426 upgrade-required flow for events stream + error handling. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/add/+page.svelte | Handles 426 upgrade-required flow during project creation. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/usage/+page.svelte | Implements “Change plan” navigation to new billing route. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/integrations/+page.svelte | Handles 426 upgrade-required flow for integrations actions. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/add/+page.svelte | Handles 426 upgrade-required flow during organization creation. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/users/+page.svelte | Handles 426 upgrade-required flow when inviting users. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/usage/+page.svelte | Implements “Change plan” navigation to billing route. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/billing/+page.svelte | Switches to new ChangePlanDialog and improves invoice link handling. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/issues/+page.svelte | Handles 426 upgrade-required flow for stack/event fetch errors. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/event/[eventId]/+page.svelte | Uses shared upgrade-required handler and redirects appropriately. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/account/notifications/+page.svelte | Adds proactive upgrade dialog launch for premium notifications. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte | Handles 426 upgrade-required flow for events list fetch errors. |
| src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte | Mounts UpgradeRequiredDialog and adds requiresPremium signal for notifications. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/schemas.ts | Adds ChangePlanRequest schema and updates several generated validators/types. |
| src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts | Adds ChangePlanRequest type and updates several generated interfaces. |
| src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-options-dropdown-menu.svelte | Replaces inline premium toast with shared upgrade-required handler. |
| src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/user-notification-settings-form.svelte | Broadens upgrade callback type to allow sync handler. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/dialogs/change-plan-dialog.svelte | Removes placeholder legacy change-plan dialog from org feature. |
| src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts | Adds /plans query and /change-plan mutation + new query keys. |
| src/Exceptionless.Web/ClientApp/src/lib/features/events/premium-filter.ts | Adds client-side premium filter detection utility. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/upgrade-required.svelte.ts | New shared 426 handling state + helpers. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/stripe.svelte.ts | New Stripe context + singleton lazy loader. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/schemas.ts | Adds change-plan form schema. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/models.ts | Adds billing model re-exports and UI state types. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/index.ts | Exposes billing module public API (components + helpers). |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/constants.ts | Adds FREE plan id constant. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/upgrade-required-dialog.svelte | New upgrade-required dialog that can open ChangePlanDialog. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/stripe-provider.svelte | Imperative Stripe Elements/PaymentElement provider for Svelte 5. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.svelte | New full-featured plan management dialog with Stripe PaymentElement + coupons. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog.stories.ts | Adds Storybook coverage for dialog states. |
| src/Exceptionless.Web/ClientApp/src/lib/features/billing/components/change-plan-dialog-harness.svelte | Adds Storybook harness with QueryClient seeding. |
| src/Exceptionless.Web/ClientApp/src/app.css | Adjusts primary foreground color variables. |
| src/Exceptionless.Web/ClientApp/package.json | Adds @stripe/stripe-js. |
| src/Exceptionless.Web/ClientApp/package-lock.json | Locks @stripe/stripe-js dependency. |
| src/Exceptionless.Web/ClientApp/.storybook/mocks/env.js | Ensures Stripe publishable key exists for Storybook billing stories. |
| src/Exceptionless.Web/ClientApp.angular/components/organization/organization-service.js | Updates legacy Angular changePlan to send JSON body (snake_case). |
| src/Exceptionless.Web/ClientApp.angular/components/billing/change-plan-controller.js | Improves error message extraction for change-plan failures. |
| src/Exceptionless.Job/Exceptionless.Job.csproj | Formatting-only change (EOF). |
| src/Exceptionless.Core/Exceptionless.Core.csproj | Upgrades Stripe.net dependency to v51. |
| docs/billing-stripe-integration.md | Adds architecture/config/API docs for the new Stripe integration. |
| .vscode/settings.json | Fixes VS Code TypeScript SDK setting key. |
| .github/hooks/follow-up.json | Adds a repo hook configuration file for follow-up/stop behavior. |
| .aspire/settings.json | Formatting-only change (EOF). |
Files not reviewed (1)
- src/Exceptionless.Web/ClientApp/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import { UpgradeRequiredDialog } from '$features/billing'; | ||
| import { invalidatePersistentEventQueries } from '$features/events/api.svelte'; | ||
| import { filterUsesPremiumFeatures } from '$features/events/premium-filter'; | ||
|
|
||
| const requiresPremium = $derived(filterUsesPremiumFeatures(page.url.searchParams.get('filter'))); | ||
| import { buildIntercomBootOptions, IntercomShell } from '$features/intercom'; |
| async function onChangePlanClose() { | ||
| const retry = upgradeRequiredDialog.retryCallback; | ||
| showChangePlan = false; | ||
|
|
||
| if (retry) { |
| var plan = _billingManager.GetBillingPlan(model.PlanId); | ||
| if (plan is null) | ||
| return Ok(ChangePlanResult.FailWithMessage("Invalid PlanId.")); | ||
| { | ||
| ModelState.AddModelError(nameof(model.PlanId), "Invalid PlanId."); | ||
| return ValidationProblem(ModelState); | ||
| } |
| **Body** (JSON, preferred): | ||
|
|
||
| | Field | Type | Description | | ||
| | --- | --- | --- | | ||
| | `planId` | string | Target plan ID (e.g., `EX_MEDIUM`, `EX_LARGE_YEARLY`) | | ||
| | `stripeToken` | string? | `pm_` PaymentMethod ID (Svelte) or `tok_` token (Angular) | | ||
| | `last4` | string? | Last 4 digits of card (display only) | | ||
| | `couponId` | string? | Stripe coupon code | |
Summary
ChangePlanDialogandStripeProvidergetBilling,changePlan,getInvoicesChanges