Skip to content

feat: Next generation Stripe billing integration#2161

Open
niemyjski wants to merge 37 commits intomainfrom
feature/next-stripe
Open

feat: Next generation Stripe billing integration#2161
niemyjski wants to merge 37 commits intomainfrom
feature/next-stripe

Conversation

@niemyjski
Copy link
Copy Markdown
Member

Summary

  • Adds new Stripe billing components: ChangePlanDialog and StripeProvider
  • Implements Stripe Checkout session creation via API
  • Adds billing features to organization API: getBilling, changePlan, getInvoices
  • Updates billing pages to use new components
  • Updates organization and invoice mappers
  • Adds Stripe integration plan document

Changes

  • New Svelte components for Stripe integration
  • New backend endpoints for billing management
  • Updated usage pages to show billing info

- 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.
Copilot AI review requested due to automatic review settings March 17, 2026 03:14
@niemyjski niemyjski force-pushed the feature/next-stripe branch from 985add7 to ee79cb9 Compare March 17, 2026 03:15
@niemyjski niemyjski self-assigned this Mar 17, 2026
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Fixed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Outdated
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Outdated
Copilot AI review requested due to automatic review settings April 16, 2026 04:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and PaymentMethod APIs.
  • 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.

Comment thread src/Exceptionless.Web/Mapping/InvoiceMapper.cs Outdated
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Outdated
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Outdated
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs
Comment thread src/Exceptionless.Web/ClientApp/src/lib/features/billing/stripe.svelte.ts Outdated
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs
Copilot AI review requested due to automatic review settings April 16, 2026 05:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/Exceptionless.Job/Program.cs Outdated
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Outdated
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Outdated
Comment thread src/Exceptionless.Web/Startup.cs
Comment thread src/Exceptionless.Web/ApmExtensions.cs
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Outdated
Copilot AI review requested due to automatic review settings April 16, 2026 15:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread .vscode/settings.json Outdated
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Outdated
Comment thread src/Exceptionless.Web/ApmExtensions.cs
Comment thread src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts Outdated
Comment thread src/Exceptionless.Web/ClientApp/src/lib/features/organizations/api.svelte.ts Outdated
niemyjski and others added 5 commits April 18, 2026 10:22
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>
niemyjski and others added 11 commits April 19, 2026 12:47
- Upgrade @stripe/stripe-js to v9.2.0.
- Update OrganizationController to use PriceId instead of Price, following changes in Stripe.net 51.x where Price became an expandable field.
- Convert errorMessage to a Svelte 5 $state rune in stripe-provider.svelte to ensure reactivity.
Replace generic toast errors with proper upgrade confirmation dialog
matching the legacy Angular UI behavior:
- Modal shows backend ProblemDetails message (same text as legacy)
- 'Upgrade Plan' button navigates to billing page with ?changePlan=true
- 'Cancel' button dismisses without navigation
- Dialog rendered globally in app layout, triggered from any page

Fixed pages:
- Add Project: was showing generic error, now shows upgrade dialog
- Event Detail: was a TODO comment, now properly handles 426
- Stack Promote: was showing hardcoded message, now uses ProblemDetails
- Users Invite: was missing 426 handling entirely
- Add Organization: was missing 426 handling entirely
- Integrations (webhook + Slack): was missing 426 handling entirely

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e native <select> with onchange handler instead of bits-ui Select to\navoid portal/focus-trap conflicts inside Dialog. Wrap form mutations in\nuntrack() to prevent reactive cycles between $effect init and form state.\nClean up label layout with separate subtitle line.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>"
…maries

- Implements a more modern UI with a monthly/yearly billing toggle.
- Automatically groups plans by tier and provides automatic yearly savings labels.
- Adds a dynamic change summary that displays prorated details and plan transitions.
- Integrates coupon management directly into the subscription workflow.
- Improves responsive layout with a scrollable content area and clearer typography.
Two bugs in OrganizationController prevented plan changes:

1. GetPlansAsync() mutated the singleton BillingPlans list directly
   instead of cloning it first. When a global admin loaded plans, the
   org's billing data (e.g., price=0) permanently corrupted the
   singleton, causing wrong prices for all subsequent requests.
   Fix: always .ToList() before modifying.

2. ChangePlanAsync had [Consumes("application/json")] but the frontend
   sends plan parameters as query strings with no request body, so
   ASP.NET Core rejected the request with a 404.
   Fix: remove the Consumes attribute.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Implements a Storybook harness and stories to test various billing scenarios, including upgrades, downgrades, and coupon applications.
- Configures the Stripe provider for manual payment method creation to support the deferred-intent flow.
- Refines the tier selection logic and plan transition summaries in the dialog.
- Adds error reporting via Exceptionless for plan change failures.
- Replace custom billing interval buttons with Tabs component.
- Standardize plan selection buttons and status labels using Button and Badge components.
- Clean up unnecessary divider comments in Storybook file.
- Introduces a "Stop" hook that blocks termination unless explicitly allowed.
- Ensures that the agent provides a summary of completed and pending items and prompts for follow-up questions before stopping.
- Add `initialCouponCode`, `initialCouponOpen`, and `initialFormError` props to allow pre-configuring the dialog state.
- Implement new Storybook stories covering coupon application, invalid coupons, payment failures, and blocked downgrades.
- Standardize the applied coupon display using the Alert component.
- Improve layout padding in the dialog content area for better alignment.
- Format prices with thousands separators and fix pluralization for user counts.
- Adjust "Most popular" badge logic to only show for plan upgrades.
- Explicitly render initial form errors using the ErrorMessage component instead of setting form state.
@github-actions
Copy link
Copy Markdown

Code Coverage

Package Line Rate Branch Rate Complexity Health
Exceptionless.Core 66% 60% 7629
Exceptionless.Insulation 25% 23% 203
Exceptionless.Web 57% 43% 3676
Exceptionless.AppHost 26% 14% 55
Summary 61% (11915 / 19501) 54% (5954 / 11118) 11563

- Remove price === 0 exclusion from shouldIncludeInTiers; the FREE_PLAN_ID
  guard already handles the free plan; excluding on price breaks orgs with
  admin-negotiated \/mo plans (e.g. EX_SMALL with billing_price=0)
- Guard yearly savings % calculation against division-by-zero when a tier
  has a monthly price of \ (NaN/Infinity was suppressing the Save badge)
- Add ErrorPlanChangeFailed Storybook story (15th story)
- Add Stripe test publishable key to Angular app config
The exception object is already passed as the first parameter to
LogError/LogWarning which captures the full exception including message.
Adding ex.Message as a template parameter causes it to appear twice in
structured log output.
Covers architecture, API endpoints, plan structure, frontend components,
Stripe SDK v47→v51 migration notes, known limitations, Storybook stories,
and security considerations.
White text on lime green primary (hsl(96 64% 46%)) achieved only 2.30:1
contrast ratio, failing WCAG 2.2 AA minimum (4.5:1 for normal text).
Fixed by setting --primary-foreground to dark text (hsl(0 0% 10%)) in
both light and dark modes, yielding 8.15:1 contrast ratio.

Also removes unused svelte-stripe package — the billing dialog uses
@stripe/stripe-js directly via the imperative Elements API.
- Accept change-plan params from JSON body (new Svelte client) with
  query string fallback for backwards compat with legacy Angular client.
  Stripe PaymentMethod sent in body avoids exposure in server/proxy logs.
- Apply coupon to existing-customer subscription update/create paths
  (was previously only applied to new-customer path)
- Narrow catch(Exception) to catch(StripeException) in price fetch loop
  so programming errors propagate rather than being silently swallowed
- InvoiceMapper: use OrdinalIgnoreCase for Stripe status comparison
- stripe.svelte.ts: reset cached promise on loadStripeOnce failure so
  callers can retry without a full page refresh
- api.svelte.ts: import ChangePlanParams from billing/models.ts instead
  of redefining the same shape (removes duplication)
- .vscode/settings.json: fix TypeScript SDK key (js/ts.tsdk.path ->
  typescript.tsdk)
…tails, regenerate client types

- Remove [FromQuery]/[FromBody] dual binding from ChangePlanAsync; endpoint now accepts only a JSON body
- Add [Required] to PlanId on ChangePlanRequest; [ApiController] now returns 400 ProblemDetails automatically for missing plan_id
- Remove stale XML comments about Angular/Svelte dual-binding compat
- Fix logger message template: {ErrorMessage} -> {Message}
- Update Angular org service to POST JSON body instead of query params
- Regenerate ChangePlanRequest from OpenAPI spec (plan_id: string required); remove manual ChangePlanParams
- Handle ProblemDetails in change-plan-dialog catch block
- Fix catch (err) -> catch (error: unknown) in stripe.svelte.ts
- Update organizations.http test to use JSON body
Billing disabled and invalid org now return 404 instead of 200+fail.
Invalid plan ID returns 422 ValidationProblem. Business logic failures
(downgrade constraints, missing billing info, Stripe errors) remain as
200+ChangePlanResult for backwards compatibility with both frontends.
…ions

- Default plan upgrades to yearly billing to promote savings.
- Improve coupon error handling by auto-focusing the input and showing inline errors on failure.
- Add toast notifications for plan change success and failure states.
- Parallelize Stripe customer update and subscription listing tasks in the backend.
- Transition dialog visibility to use an `onclose` callback instead of `bind:open` for more predictable state management.
Comment thread src/Exceptionless.Web/Controllers/OrganizationController.cs Dismissed
- 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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 45 to 48
.AddYamlFile("appsettings.yml", optional: true, reloadOnChange: true)
.AddYamlFile($"appsettings.{environment}.yml", optional: true, reloadOnChange: true)
.AddYamlFile($"appsettings.Local.yml", optional: true, reloadOnChange: true)
.AddCustomEnvironmentVariables()
Comment on lines 76 to 80
if (response.status === 426) {
toast.error(
'Promote to External is a premium feature used to promote an error stack to an external system. Please upgrade your plan to enable this feature.'
handleUpgradeRequired(
{ status: 426, title: 'Promote to External is a premium feature. Please upgrade your plan to enable this feature.' } as never,
stack.organization_id
);
Comment on lines +32 to 34
async function handleChangePlan() {
await goto(`/next/organization/${page.params.organizationId}/billing?changePlan=true`);
}
Comment on lines +108 to +115
**Props**:

- `open: boolean` — bindable dialog visibility
- `organization: ViewOrganization` — current org data
- `initialCouponCode?: string` — pre-fill coupon
- `initialCouponOpen?: boolean` — open coupon input on mount
- `initialFormError?: string` — show error message on mount

Comment on lines +45 to +48
async function handleChangePlan() {
if (organization.current) {
await goto(`/next/organization/${organization.current}/billing?changePlan=true`);
}
Comment on lines +55 to +63
**Parameters** (query string):

| Param | 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 |

Comment on lines +166 to +169
1. **Coupon not applied for existing customers changing plans** — Pre-existing limitation (not a regression). The coupon is only applied when creating a new Stripe customer.
2. **Potential orphaned Stripe customers** — If subscription creation fails after customer creation, a retry would create a duplicate Stripe customer. Mitigated by the low likelihood of this failure path.
3. **N+1 price fetches in invoice view** — Each unique price ID in an invoice makes a separate Stripe API call. Mitigated by a per-request cache (`priceCache`). Most invoices have 1-3 distinct prices.
4. **svelte-stripe package unused** — Listed in `package.json` but bypassed due to Svelte 5 incompatibility. Only `@stripe/stripe-js` is used directly.
Comment thread .vscode/launch.json
Comment on lines 5 to 9
"type": "aspire",
"request": "launch",
"name": "Aspire",
"program": "${workspaceFolder}/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj"
"program": ""
},
Comment on lines +446 to +451
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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

3 participants