Skip to main content

The account hub

When billing.enabled is on, the hub mounts under config('account.prefix') (default account/billing) behind your web + auth middleware:

RouteScreen
/Overview + tier summary
/subscriptionState, next-invoice preview, cancel / resume, portal link
/planSubscribe or in-app swap with a proration preview, and the coupon field
/payment-methodsList, set-default, remove, add
/invoicesHistory + ownership-checked PDF download
/usageMetered dimensions
/usage/historyPast periods + add-on top-up timeline
/recoveryDunning recovery surface
/dangerImmediate cancel
/portalRedirect to the provider's hosted billing portal
/checkout/returnWhere a customer lands coming back from the provider

See Subscriptions for the subscribe/swap flow behind /plan.

What Subscribe does depends on the driver, and the screen does not change. Under a driver whose provider runs the cycle it opens that provider's hosted checkout. Under a driver this package bills itself it writes an intent and sends the customer to establish a mandate through a first payment; the subscription becomes real when that mandate settles, and until then the screen shows an activating state and polls.

/checkout/return follows the same split. Under a hosted checkout it reconciles the subscription from the provider, which is a courtesy for a customer who beats the webhook home. Under a driver this package bills itself there is nothing at the provider to read back, so it reconciles nothing and simply hands the customer to the subscription screen. The webhook is the durable path either way.

The coupon field sits on /plan, and the status under it means one thing: would the driver you configured actually apply this code? Not "does the code exist" — see Subscriptions for which catalog answers under which driver, and why a code can be valid in your configuration and still be reported as invalid.

Canceling can take two clicks if you want it to. account.cancel_requires_confirmation arms the cancellation on /subscription and carries it out on the second click, with a way back in between. It is off by default and the default does not move: canceling here is reversible, and for the person doing it less friction is the better behavior.

Add the shell banner to your layout — it renders nothing for a healthy account:

<x-billing::banner />

Every screen renders inside a publishable app shell (layouts/account.blade.php): a grouped sidebar navigation with the active item marked, a skip link to the main content, a typed document title, and a POST-logout form shown only when your app registers a logout route. It needs no UI-kit dependency; publish billing-views to replace it with your own design system's shell.

If an external merchant of record owns billing (an app-store subscription, an external portal), set billing.link_out (env BILLING_LINK_OUT) to that portal's URL: the plan screen then links out to it and suppresses the in-app checkout it is not the merchant of record for. On a native runtime, set billing.runtime=native (env BILLING_RUNTIME) to hide flows an app store forbids in-app.

Hosting your own screens in the hub

The hub navigation is config-driven, and the hub hosts screens it does not own. To slot one of your own app or auth screens — sessions, connections, set-password, an onboarding step — into the hub, register its route in billing.navigation (an optional group places it in a labeled section, billing::account.nav.group.<group>):

// config/billing.php
'navigation' => [
'sessions' => ['label' => 'account.sessions', 'route' => 'app.sessions', 'group' => 'account', 'order' => 60],
],

The entry appears in the navigation only once that route actually exists, so the same config can name a section your app builds later — an unregistered route (or one that needs parameters the hub cannot supply) is silently dropped, never rendered as a broken link. The package ships no ancillary-screen classes of its own; it only slots your route into the shell.


← Back to the documentation index