Skip to content

Subscription Lifecycle

NuxtBase treats subscriptions as organization-level state.

The active organization is the billing subject, and only organization managers can mutate that state.

For the shipped template, the practical lifecycle is:

  1. an organization starts on free
  2. a manager opens checkout for a paid plan
  3. Stripe completes checkout and returns the user to /dashboard/billing
  4. Stripe webhooks create or update the local subscription record
  5. managers can later change plan, schedule cancellation, or resume
  6. if no operable subscription remains, the organization falls back to free

The app considers only these Stripe-style states “operable” for billing actions:

const operableBillingSubscriptionStatuses = [
"active",
"trialing",
"past_due",
];

That affects:

  • which subscription the app treats as current
  • whether plan changes are allowed
  • whether cancel and resume actions are allowed
  • whether the organization is treated as paid or free

If the current record is outside those states, billing mutations return a conflict instead of guessing.

Checkout starts from POST /api/billing/checkout.

Important shipped behavior:

  • it requires an active organization
  • it requires the current user to be an owner or admin
  • it rejects checkout if an operable subscription already exists
  • it defaults success and cancel return URLs to the billing page

The default return URLs are:

/dashboard/billing?checkout=success
/dashboard/billing?checkout=canceled

The endpoint also rejects cross-origin return URLs. They must use the same origin as NUXT_PUBLIC_SITE_URL.

Changing from one paid plan to another uses a dedicated mutation, not a second checkout session.

The shipped behavior is:

  1. find the current operable subscription for the active organization
  2. verify its state allows plan changes
  3. update the Stripe subscription item price
  4. sync the new price back into the local billing tables immediately

The Stripe update uses:

proration_behavior: "always_invoice"

That matters because upgrades or downgrades can generate immediate proration invoices depending on your Stripe configuration.

NuxtBase does not immediately delete a paid subscription when the user clicks cancel in the app.

Instead:

  • cancel sets cancelAtPeriodEnd = true
  • resume sets cancelAtPeriodEnd = false

So the common user-facing pattern is “scheduled to cancel at period end”, not “subscription already gone”.

The dashboard also exposes a “manage subscription” action for paid plans. That opens the Stripe customer portal when a billing customer exists.

If the organization does not yet have a linked billing customer, the portal endpoint returns 404 instead of fabricating a management session.

  1. start checkout from the active organization as an owner or admin
  2. confirm the billing page reflects the paid plan after webhook sync
  3. change from monthly to yearly or between paid plans
  4. schedule a cancellation and verify the period-end state appears in the UI
  5. resume before period end and confirm the cancellation flag clears
  6. confirm regular members can view billing but cannot mutate it