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).

Routes and webhooks

Every route the package registers, with its verb, default URL and name. Use this page when you link to a bundled page, register a webhook or redirect URI at Mollie, or put your own middleware in front of the billing pages.

Before you start

Every URL below is the default. The route names are stable: link to those, never to a hard-coded path.

Dashboard Toggle (standalone.dashboards.*) URL key (standalone.url.*) Default prefix
Subscription subscription subscription-dashboard billing/dashboard
Payments payments payments-dashboard billing/payments
Payment methods payment_methods payment-methods-dashboard billing/payment-methods
Credit credit credit-dashboard billing/credit
Profile profile profile-dashboard billing/profile
Invoices invoices invoices-dashboard billing/invoices
Plans (admin) tenant_subscriptions tenant-subscriptions-dashboard billing/admin/subscriptions
Subscribers (admin) tenant_subscribers tenant-subscribers-dashboard billing/admin/subscribers
Charges (admin) tenant_charges tenant-charges-dashboard billing/admin/charges
Invoices (admin) tenant_invoices tenant-invoices-dashboard billing/admin/invoices
Mollie connection (admin, Connect only) tenant_connect tenant-connect-dashboard billing/admin/connect
Application fees (admin, Connect only) tenant_fees tenant-fees-dashboard billing/admin/fees

The two invoice dashboards also need larsasub.invoicing.enabled (off by default), and the two Connect-only dashboards need connect.enabled. See Standalone dashboards.

Subscriber dashboards

The pages a customer uses to manage their own subscription, payments, payment methods, credit, profile and invoices.

Verb URL Route name
GET /billing/dashboard larsasub.my-subscription.view
POST /billing/dashboard/create larsasub.my-subscription.create
PUT /billing/dashboard/cancel/{subscription} larsasub.my-subscription.cancel
DELETE /billing/dashboard/terminate/{subscription} larsasub.my-subscription.terminate
POST /billing/dashboard/retry-first-payment/{subscription} larsasub.my-subscription.retry-first-payment
PUT /billing/dashboard/change/{currentSubscription}/{newSubscription} larsasub.my-subscription.change
GET /billing/dashboard/terms larsasub.my-subscription.terms
GET /billing/payments larsasub.my-payments.view
GET /billing/payments/charges/{charge}/start-payment larsasub.my-payments.manual-charge-payment
GET /billing/payments/single-payments/{singlePayment}/start-payment larsasub.my-payments.single-payment
GET /billing/payments/charges/{charge}/invoice larsasub.my-payments.charge-invoice
GET /billing/payments/single-payments/{singlePayment}/invoice larsasub.my-payments.single-payment-invoice
GET /billing/payment-methods larsasub.my-payment-methods.view
POST /billing/payment-methods/{paymentMethod} larsasub.my-payment-methods.create
DELETE /billing/payment-methods/{mandateId} larsasub.my-payment-methods.delete
PATCH /billing/payment-methods/subscription/{subscriptionId}/mandate/{mandateId} larsasub.my-payment-methods.switch-mandate
GET /billing/credit larsasub.my-credit.view
GET /billing/profile larsasub.my-profile.view
PUT /billing/profile larsasub.my-profile.update
GET /billing/invoices larsasub.my-invoices.view
GET /billing/invoices/invoices/{invoice}/download larsasub.my-invoices.download
GET /billing/invoices/credit-notes/{creditNote}/download larsasub.my-invoices.download-credit-note

The route named terminate deletes, and only a failed first payment. It calls BillableSubscriptionService::terminateFailedSubscription(), which deletes a subscription whose first payment failed — and refuses while a claimed payment attempt is still live. The admin hammer terminateSubscription() is deliberately not routed. See Subscriptions.

The retry-first-payment route refuses a plan that is deleted or no longer active, because the frozen price is no longer on sale; the dashboard hides the button in that case and offers the plan picker instead.

my-payment-methods.create starts the €0,01 mandate verification. A repeat click while that verification is still being started answers a JSON request with 409 and a "wait a moment and reload" message (a browser request gets the message back as a validation error) — see Setting up a mandate.

Management dashboards

The operator pages: plans, subscribers, charges and invoices. They are mode-aware: in Connect mode they belong to the tenant owner and show that tenant's data; in simple mode they are operator management, authorized through policies.admin_role. See Admin dashboard authorization.

Verb URL Route name
GET /billing/admin/subscriptions larsasub.tenant-subscriptions.view
POST /billing/admin/subscriptions larsasub.tenant-subscriptions.store
PUT /billing/admin/subscriptions/{subscription} larsasub.tenant-subscriptions.update
PATCH /billing/admin/subscriptions/{subscription}/toggle larsasub.tenant-subscriptions.toggle
DELETE /billing/admin/subscriptions/{subscription} larsasub.tenant-subscriptions.destroy
POST /billing/admin/subscriptions/{subscription}/change-targets larsasub.tenant-subscriptions.change-targets
GET /billing/admin/subscribers larsasub.tenant-subscribers.view
GET /billing/admin/charges larsasub.tenant-charges.view
GET /billing/admin/charges/lookup larsasub.tenant-charges.lookup
GET /billing/admin/invoices larsasub.tenant-invoices.view
GET /billing/admin/invoices/invoices/{invoice}/download larsasub.tenant-invoices.download
GET /billing/admin/invoices/credit-notes/{creditNote}/download larsasub.tenant-invoices.download-credit-note

Connect mode only:

Verb URL Route name
GET /billing/admin/connect larsasub.tenant-connect.view
PATCH /billing/admin/connect/payment-methods/{paymentMethod} larsasub.tenant-connect.toggle-payment-method
GET /billing/admin/fees larsasub.tenant-fees.view

tenant-connect.toggle-payment-method switches a payment method on or off for the tenant; what it checks is under Tenant availability.

Post-payment redirects

Where the customer's browser lands when it comes back from the provider. These routes are always registered, whatever the dashboard toggles say.

Verb URL Route name
GET /larsasub/redirects/first-payment/{charge} larsasub.redirect.first-payment
GET /larsasub/redirects/manual-charge-payment/{charge} larsasub.redirect.manual-charge-payment
GET /larsasub/redirects/mandate-setup/{charge?} larsasub.redirect.mandate-setup
GET /larsasub/redirects/single-payment/{singlePayment} larsasub.redirect.single-payment

These URLs are embedded in payments at the provider, so they run on the plain web middleware, outside standalone.middleware and standalone.domain. A customer returning from their bank in a fresh browser session has to land somewhere.

Each one forwards to the destination configured under larsasub.redirect: a named route (the default) or an invokable class that computes the URL from the context that key carries — a RecurringCharge on the three charge keys, a SinglePayment on single_payment, the tenant model on connect_callback. The table under Redirect routes is the authority on which key gets what.

These endpoints are unauthenticated, and the bound model is not ownership-checked. Route on the model; never render its data. Read the security note under Redirect routes.

{charge?} on the mandate-setup route is optional and must stay so: that URL is baked into every €0,01 mandate verification before the customer leaves for their bank, so any verification still open when you deploy carries the old, segment-less URL.

Mollie Connect

The OAuth onboarding and disconnect endpoints. They are registered only when connect.enabled is true, under the /api prefix.

Verb URL Route name
GET /api/larsasub/connect/mollie/authorize/{tenant} larsasub.mollie.connect.authorize
GET /api/larsasub/connect/mollie/callback larsasub.mollie.connect.callback
GET /api/larsasub/connect/mollie/complete larsasub.mollie.connect.complete
DELETE /api/larsasub/connect/mollie/disconnect larsasub.mollie.connect.disconnect
POST /api/larsasub/connect/mollie/cancel-disconnect larsasub.mollie.connect.cancel-disconnect

The OAuth routes

authorize is throttled (10 per minute) and only lets the tenant's owner in. It also takes a tenant that is already connected: that is how a tenant re-authorises to grant a scope added since — see Re-authorising a connected tenant. connect.authorize_route names this route by default; point it at your own route if you wrap the start of the flow.

callback is the redirect URI you register in your Mollie Connect app:

https://your-app.example/api/larsasub/connect/mollie/callback

It is built from config('app.url') (or from LARSASUB_NGROK_DOMAIN in a local/development environment). It deliberately runs without the dashboard middleware and without the standalone.domain constraint, because the OAuth return can arrive on a different origin than the one the user authorized from. It does not complete the connection: it hands off to complete, which runs on the authenticated origin. See The OAuth flow.

The disconnect routes

disconnect and cancel-disconnect require the tenant owner and take the tenant's key as tenant, in the query string or the body. This is the URL the bundled dashboard is given:

route('larsasub.mollie.connect.disconnect', ['tenant' => $tenant->getKey()]);

disconnect takes mode:

mode What it does
drain (default) Requests the disconnect: the account drains, or goes at once when nothing is under way
now Removes the connection at once and flags what is still under way

cancel-disconnect calls a draining disconnect off.

Both answer {"success": true, "disconnect": {…}} with DisconnectImpact::toArray(), or 422 with a translated error when there is nothing to disconnect, nothing draining, or an unknown mode. Key on the status code, not the message. See Disconnecting a tenant.

Provider webhooks

The URLs Mollie calls when a payment changes status. The package puts the right webhook URL on every payment it creates, so there is nothing to register at Mollie for these.

They are unauthenticated by design — the provider calls them — with CSRF verification removed and an IP throttle from larsasub.webhooks.rate_limit (LARSASUB_WEBHOOK_RATE_LIMIT, default '300,1'; null disables it). A throttled-away webhook is a missed status until Mollie retries, so keep the limit generous.

POST /api/larsasub/webhooks/mollie/{method}/first-payment
POST /api/larsasub/webhooks/mollie/{method}/recurring-charge
POST /api/larsasub/webhooks/mollie/{method}/single-payment
POST /api/larsasub/webhooks/mollie/{method}/manual-charge-payment

{method} is the Mollie method key. The route names follow the same pattern: larsasub.mollie.{method}.process-first-payment, …process-recurring-charge, …process-single-payment and …process-manual-charge-payment.

The set registered depends on what the method supports:

Method {method} Webhooks
Credit card, PayPal — recurring in their own right creditcard, paypal first-payment, recurring-charge, single-payment, manual-charge-payment
SEPA Direct Debit directdebit first-payment, recurring-charge
iDEAL, Bancontact, Apple Pay, KBC/CBC, Belfius, EPS, TWINT ideal, bancontact, applepay, kbc, belfius, eps, twint single-payment, manual-charge-payment

iDEAL opening a SEPA mandate does not land on the iDEAL routes. When first_payment_method_id points at iDEAL (the default for direct debit), the checkout runs at Mollie in iDEAL but the charge belongs to Direct Debit, so its webhook arrives on larsasub.mollie.directdebit.process-first-payment. The same holds for Bancontact as the mandate opener. Only credit card and PayPal own a first-payment route under their own key.

Mandate-setup payments (the €0,01 verification) have no dedicated webhook: they arrive on the recurring method's first-payment webhook.

There is deliberately no public route that starts a manual charge payment. That is a money action and lives behind auth and policy in the payments dashboard (larsasub.my-payments.manual-charge-payment), never in the webhook group.

The routes come from the provider configured in larsasub.provider. A provider of your own registers its own webhook routes — see Adding your own provider.

Middleware

Which middleware each group runs on, and which config keys reach it.

Routes Middleware standalone.domain applies
Every dashboard route standalone.middleware — the complete stack Yes
Connect authorize, complete, disconnect, cancel-disconnect web (always, from the outer route group) plus standalone.middleware, and throttle:10,1 on authorize. Removing web from standalone.middleware does not make these stateless Yes
Connect callback web only No
Post-payment redirects web only No
Provider webhooks The webhook throttle only, no CSRF No

standalone.middleware (default ['web', 'auth', 'verified']) is the complete stack for every dashboard route — nothing is wrapped around it. standalone.guard selects the guard the package reads the user from, and standalone.domain can constrain the dashboards to their own hostname. See Standalone dashboards.