LarsaSub is launching soon. Purchases and account registration open shortly.

Documentation for larsasub v0.38.2. On an older version? Your release notes ship with the package (CHANGELOG.md).

Standalone dashboards

LarsaSub ships twelve ready-made pages — six for the subscriber, six for whoever manages the plans — built as self-contained Vue apps with their own compiled CSS. They work without any host asset pipeline, and you can switch off every one of them if you would rather build your own. Read this page when you install the package, put the pages behind your own authentication, or restyle them.

What ships

Six dashboards serve the subscriber and six serve whoever manages the plans. Each is a Blade page that mounts a compiled Vue app, linked to the others by a shared navigation bar, and each can be switched off on its own.

Subscriber dashboards

Key Default URL What it does
subscription billing/dashboard The current subscription: subscribe, change plan, cancel, retry or abandon a subscription whose first payment failed, read and accept the terms
payments billing/payments Payment history, the manual "pay now" recovery flow for charges and single payments, per-payment invoice download
payment_methods billing/payment-methods Add a payment method (mandate setup), remove one, switch a subscription's mandate
credit billing/credit Credit balance and ledger, with expiry dates
profile billing/profile The invoice profile (personal/business, address, country, VAT number) and, with the VIES check on, the status of that number
invoices billing/invoices Invoices and credit notes, with PDF download

The subscribe card names what the click collects

Each plan on the subscription dashboard carries the quote of its first charge (SubscriptionCostService::quoteFirstCharge(), as quote in every availableSubscriptions entry), and the card prints that — not the catalogue price:

On a paid plan the button reads "Order with obligation to pay"; a free plan keeps "Get started". Both are built into the bundle, like every frontend.json text — see Multilang.

A card the quote cannot fill shows no button but the reason, and the server refuses the same subscribe:

In Connect mode the subscribe section steps aside while the tenant's Mollie disconnect drains (Disconnecting a tenant).

Management dashboards

These are mode-aware: in Connect mode they are the tenant owner's view of their own tenant; in simple mode they are operator management over the global plans.

Key Default URL What it does
tenant_subscriptions billing/admin/subscriptions Define, edit and delete plans, toggle them, manage change targets
tenant_subscribers billing/admin/subscribers Who is subscribed to what. A checkout abandoned before its first payment (first_payment_abandoned) is not listed or counted
tenant_charges billing/admin/charges Charge buckets — planned, in progress, needing attention, owed after chargeback, failed, recently paid — with totals and a lookup by payment reference. The canceled first payment of an abandoned checkout stays out of the buckets; the lookup still finds it
tenant_invoices billing/admin/invoices Issued invoices and credit notes
tenant_connect billing/admin/connect The Mollie connection, re-authorizing, disconnecting (draining first) and the per-tenant payment methods — Connect mode only
tenant_fees billing/admin/fees Application fees charged — Connect mode only

The invoices and tenant_invoices dashboards additionally require larsasub.invoicing.enabled; tenant_connect and tenant_fees require connect.enabled. Their routes are not registered without it.

The Mollie website profile is not on tenant_connect yet. A tenant whose organization holds several profiles cannot pick one from the bundled page; the package's own choice stands until someone calls MollieConnectService::setProfile(). Build the picker on listProfiles() / setProfile() if your tenants need it — see Choosing the website profile.

Who can open them

Every page first passes standalone.middleware, and every action is then authorized through the package's policies against the signed-in user's billable.

Publishing the assets

The dashboards load their CSS and JavaScript from public/vendor/larsasub/. larsasub:install publishes them, and it does so on every run — run it again after each upgrade:

php artisan larsasub:install
# or only the assets
php artisan vendor:publish --tag=larsasub-assets --force

The assets are build output that must match the installed package version: a stale bundle against a newer page payload is a broken page, not a customization. That is why the install command overwrites them, unlike your published config. larsasub:doctor warns when a dashboard is enabled and the assets are missing.

Each page loads the stylesheet, the Vue runtime and its bundle with the published file's modification time appended (larsasub.css?v=…), so a browser fetches the new files after you republish instead of running a cached bundle.

Switching dashboards on and off

'standalone' => [
    'dashboards' => [
        'subscription' => env('LARSASUB_DASHBOARD_SUBSCRIPTION', true),
        'payments' => env('LARSASUB_DASHBOARD_PAYMENTS', true),
        'payment_methods' => env('LARSASUB_DASHBOARD_PAYMENT_METHODS', true),
        'credit' => env('LARSASUB_DASHBOARD_CREDIT', true),
        'profile' => env('LARSASUB_DASHBOARD_PROFILE', true),
        'invoices' => env('LARSASUB_DASHBOARD_INVOICES', true),
        'tenant_connect' => env('LARSASUB_DASHBOARD_TENANT_CONNECT', true),
        'tenant_subscriptions' => env('LARSASUB_DASHBOARD_TENANT_SUBSCRIPTIONS', true),
        'tenant_subscribers' => env('LARSASUB_DASHBOARD_TENANT_SUBSCRIBERS', true),
        'tenant_charges' => env('LARSASUB_DASHBOARD_TENANT_CHARGES', true),
        'tenant_fees' => env('LARSASUB_DASHBOARD_TENANT_FEES', true),
        'tenant_invoices' => env('LARSASUB_DASHBOARD_TENANT_INVOICES', true),
    ],
],

All twelve default to true. Switching one off unregisters its routes entirely.

Mails point at dashboards. Four subscriber mails fall back to one when their URL key is empty: notifications.payment_url (payment failed, chargeback, the dunning reminder) to the payments dashboard, and notifications.first_payment_url to the subscription dashboard. Disable those dashboards without configuring a URL and the mails lose their action button — ConfigValidator warns for exactly that combination. While a Connect tenant drains its disconnect, the four mails leave the dashboard button out, because the dashboard would refuse the payment or the retry; a URL of your own is still used. In Connect mode the admin button of a tenant admin's company mail falls back to the first of the subscribers, charges, subscriptions and connect dashboards that is registered; with those four off and no notifications.tenant_admin_url, those mails carry no admin button at all, and ConfigValidator warns too. See Action-button URLs.

URLs

Each dashboard has its own prefix under standalone.url:

'url' => [
    'subscription-dashboard' => env('LARSASUB_STANDALONE_SUBSCRIPTION_URL', 'billing/dashboard'),
    'payments-dashboard' => env('LARSASUB_STANDALONE_PAYMENTS_URL', 'billing/payments'),
    'payment-methods-dashboard' => env('LARSASUB_STANDALONE_METHODS_URL', 'billing/payment-methods'),
    'credit-dashboard' => env('LARSASUB_STANDALONE_CREDIT_URL', 'billing/credit'),
    'profile-dashboard' => env('LARSASUB_STANDALONE_PROFILE_URL', 'billing/profile'),
    'invoices-dashboard' => env('LARSASUB_STANDALONE_INVOICES_URL', 'billing/invoices'),
    'tenant-connect-dashboard' => env('LARSASUB_STANDALONE_TENANT_CONNECT_URL', 'billing/admin/connect'),
    'tenant-subscriptions-dashboard' => env('LARSASUB_STANDALONE_TENANT_SUBSCRIPTIONS_URL', 'billing/admin/subscriptions'),
    'tenant-subscribers-dashboard' => env('LARSASUB_STANDALONE_TENANT_SUBSCRIBERS_URL', 'billing/admin/subscribers'),
    'tenant-charges-dashboard' => env('LARSASUB_STANDALONE_TENANT_CHARGES_URL', 'billing/admin/charges'),
    'tenant-fees-dashboard' => env('LARSASUB_STANDALONE_TENANT_FEES_URL', 'billing/admin/fees'),
    'tenant-invoices-dashboard' => env('LARSASUB_STANDALONE_TENANT_INVOICES_URL', 'billing/admin/invoices'),
],

The full route table, with names and verbs, is in Routes and webhooks.

Two presentation options apply to all of them:

'show_back' => env('LARSASUB_STANDALONE_SHOW_BACK', true),   // render a "back" link
'back' => env('LARSASUB_STANDALONE_BACK_URL', '/'),          // where it points

Middleware: the complete stack

standalone.middleware is the middleware every dashboard route gets:

'middleware' => ['web', 'auth', 'verified'],   // the default

What you configure is exactly what the routes get. LarsaSub wraps nothing around it — the same way Nova, Horizon and Telescope treat their own middleware key. An entry you leave out is genuinely absent.

Keep web unless your guard is stateless. It carries the session and CSRF verification. Without it the dashboards still render while every action — cancel, change, pay, save profile — answers 419.

Replace the list for a host with its own auth middleware, without email verification, or with an extra tenant-resolution step:

'middleware' => ['web', 'auth:billing', 'tenant'],

LARSASUB_STANDALONE_MIDDLEWARE takes a comma-separated list. An emptied value falls back to the default rather than publishing billing pages openly.

The Mollie Connect routes a tenant owner uses — authorize, complete, disconnect, cancel disconnect — run on this same stack, so a host on a non-default guard is authenticated for connecting Mollie exactly as for its billing pages.

The guard

'guard' => env('LARSASUB_STANDALONE_GUARD'),   // null = the app's default guard

This names the guard the package reads the current user from. It is always read through GeneralHelpers::authUser(), never a bare auth()->user(), and authorization runs through GeneralHelpers::authorize() rather than the AuthorizesRequests trait — because the Gate resolves its user from the default guard, so $this->authorize() would deny the very user the page rendered for on a host stack that never calls shouldUse().

Point this at a non-default guard and you must authenticate against that guard in the middleware too (auth:billing). Otherwise the route authenticates one user and the package reads another: the pages render and every action denies. ConfigValidator warns at boot and larsasub:doctor reports it, because that mistake looks like a working install right up until the first button.

Domain

'domain' => env('LARSASUB_STANDALONE_DOMAIN'),   // e.g. 'billing.example.com'

null (default) leaves the routes unconstrained. The Connect authorize, complete, disconnect and cancel-disconnect routes follow the same domain constraint, so a subdomain-scoped billing area keeps its OAuth flow on the same origin. The OAuth callback does not — see below.

Routes that are deliberately excluded

Three kinds of route are outside standalone.middleware and standalone.domain, on purpose:

Changing these through config would be a way to accidentally break payment collection, so none of them is configurable.

List bounding

'page_size' => env('LARSASUB_STANDALONE_PAGE_SIZE', 50),
'list_limit' => env('LARSASUB_STANDALONE_LIST_LIMIT', 500),

page_size paginates the plain lists (invoices and credit notes, on both invoice dashboards). list_limit caps the client-side-filtered lists (subscribers, charge buckets) at the most recent N, with a visible truncation notice in the UI — an unbounded ->get() over a table that only grows eventually renders megabytes of JSON into every page view. The totals on the charges dashboard are counted over everything, not over the capped slice.

Theming

The published assets in public/vendor/larsasub/ are self-contained: a full Tailwind build over the package's own components (larsasub.css), the bundled Vue runtime (no CDN), and the dashboard bundles with their component styles inlined. The dashboards render correctly with no host CSS at all.

Host theming is optional and pipeline-agnostic. standalone.theme_view (LARSASUB_STANDALONE_THEME_VIEW, default null) names a Blade view that is included in the dashboard <head> after larsasub.css:

'theme_view' => 'larsasub-theme',

Put whatever your asset pipeline uses in it:

{{-- resources/views/larsasub-theme.blade.php --}}
@vite(['resources/css/app.css'])

The view is included with @includeIf, so a name that does not resolve renders nothing rather than an error — check the page source if your theme does not apply.

Theme tokens

The dashboards read shadcn-style CSS custom properties, defined on :root and again on .dark. A stylesheet that defines them re-themes the pages, and host-defined tokens win because the view is included after the bundled CSS. larsasub.css defines --background, --foreground, --card, --popover, --primary, --secondary, --muted, --accent and --destructive (each with a -foreground pair), plus --border, --input, --ring and --radius. The bundled pages lean mostly on --background, --foreground, --border, --card, --muted, --primary, --destructive and --ring:

:root { --primary: #fe9900; --primary-foreground: #0F172A; }
.dark { --primary: #fe9900; --primary-foreground: #0F172A; }

Dark mode is the .dark class on <html>. The layout sets it from the visitor's own choice (a toggle in the dashboard layout, remembered in the browser) or, without one, from the system preference.

If you want to restyle package markup with your own Tailwind utilities, a Tailwind v4 host can also scan the bundles:

@source '../../public/vendor/larsasub/*.js';

That is optional — it is not needed for correct rendering.

Emails are themed separately; see Notifications.

Building your own

Switch the dashboards off and drive the services directly — that is the headless path, and everything the dashboards do is available through the public API.

If you want the Vue components but your own build:

php artisan larsasub:install --vue-components
# or
php artisan vendor:publish --tag=larsasub-vue-components

The components land in resources/vendor/larsasub/js/components, next to the utils, types, composables and frontend translations they import, so they build as published. The stylesheet whose tokens their classes use comes along as resources/vendor/larsasub/css/larsasub.css — see Using the Vue sources in your own build. Published SFC sources are never overwritten by a later install — once published they are yours to edit. Restyling through theming needs none of this.

When you build your own subscriber UI, four contracts are easy to get wrong: