Documentation for larsasub v0.38.2.
On an older version? Your release notes ship with the package (CHANGELOG.md).
Quick start
This page takes you from composer require to a first paying subscriber on a
single Mollie account, then shows what changes when a team pays, when you bill
through Mollie Connect, and with dual billing. Setup holds the
details of each installation step; Configuration has every
setting.
- Before you start
- Install the package
- Make a model billable
- Configure the environment
- Activate payment methods
- Run the scheduler and a queue worker
- Subscribe a customer
- Check access
- Billing a team or a company
- Mollie Connect
- Dual billing
- The bundled dashboards
- Commands you will use
- Next steps
Before you start
| You need | Why |
|---|---|
| PHP ^8.3, Laravel ^12.11 or ^13.0 | The package's own requirements |
| A Mollie account and API key | Every payment runs through Mollie; a test_ key keeps everything in test mode |
| A public HTTPS URL | Mollie reports every payment's outcome to your webhooks. On localhost, use a tunnel (see Configure the environment) |
| Cron and a queue worker | Charges are planned and collected by scheduled commands, and notifications are queued |
The full checklist for a live installation is in Before you install.
Install the package
LarsaSub is not on Packagist.
composer requireresolves only after you have registered the private repository on larsasub.eu and authenticated with your license token; without that, composer reports "package not found". See the installation guide on larsasub.eu/docs.
composer require peters-development/larsasub
php artisan larsasub:install
larsasub:install publishes the configuration and the dashboard assets, runs
the migrations (an interactive run asks first, because migrate applies every
pending migration in your application) and seeds the payment methods, all of
them inactive. It is safe to run again. See
Installing the package for its options.
Make a model billable
The model that pays implements Contracts\BillableModel and uses three traits:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use PetersDevelopment\LarsaSub\Contracts\BillableModel;
use PetersDevelopment\LarsaSub\PaymentProviders\Mollie\Traits\IsMollieBillableModel;
use PetersDevelopment\LarsaSub\Traits\HasSubscriptions;
use PetersDevelopment\LarsaSub\Traits\IsBillableModel;
class User extends Authenticatable implements BillableModel
{
use HasSubscriptions, IsBillableModel, IsMollieBillableModel;
public function getBillableNameAttribute(): string
{
return $this->name;
}
public function getBillableEmailAttribute(): string
{
return $this->email;
}
public function getBillableIdentifierAttribute(): string
{
return (string) $this->id;
}
}
| Piece | What it does |
|---|---|
BillableModel |
The contract: the three accessors above, and getNotifiableUsers(), which IsBillableModel already implements (a Notifiable model mails itself; otherwise the billable e-mail address is used) |
IsBillableModel |
The billing relations and helpers: credit, single payments, mandates, payableCharges(), outstandingCharges() |
IsMollieBillableModel |
Required with the Mollie provider. The checkout calls getMollieCustomer(), createMollieMandate() and recordExistingMollieMandate() on your model. Without the trait the first customer who tries to pay gets an error naming the missing trait instead of a payment screen; php artisan larsasub:doctor reports it before that happens |
HasSubscriptions |
activeSubscription, hasFeature(), featureValue() and the other subscription shortcuts. Optional; the matching Contracts\HasSubscriptions interface is optional too, for your own type hints |
billable.model in config/larsasub.php defaults to App\Models\User.
Change it when another model pays.
You never create billing records by hand. LarsaSub links your model to every billing record through a
Billableregistration (inlarsasub_billables), created automatically on the first billing operation. See Billable models.
Configure the environment
# Mollie API key — a test_ key keeps every call in test mode
MOLLIE_KEY=test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Who is an admin: a role name, or e-mail addresses separated by commas.
# Empty means nobody, and plan management and the operator dashboards deny everyone.
LARSASUB_ADMIN_ROLE=admin@yourcompany.com
# Who receives payment-failure and cancellation alerts.
# Empty means those alerts are discarded.
LARSASUB_NOTIFICATIONS_SALES=sales@yourcompany.com
# Defaults, shown for completeness. EUR is the only supported default currency
LARSASUB_PROVIDER=mollie
LARSASUB_CURRENCY_DEFAULT=EUR
LARSASUB_NOTIFICATION_CHANNELS=mail
# Local development only: a public URL for Mollie's webhooks
LARSASUB_NGROK_DOMAIN=https://your-subdomain.ngrok.io
Every notification can be switched off on its own; see Notifications. Invoicing ships off: read Invoicing and VAT before you turn it on.
Activate payment methods
The installer seeds every supported Mollie method inactive, so nobody can pay until you choose what you accept. Activate only methods your Mollie account has enabled:
use PetersDevelopment\LarsaSub\Models\PaymentMethod;
PaymentMethod::query()
->where('provider', 'mollie')
->whereIn('provider_id', ['directdebit', 'ideal', 'creditcard'])
->get()
->each(fn (PaymentMethod $method) => $method->update(['active' => true]));
SEPA Direct Debit opens its mandate with a first payment in another method:
iDEAL by default (the method's first_payment_method_id). That method must be
enabled on your Mollie account, whether or not you activate it here for
one-time payments. The admin companions
(Nova, Filament) can do the same from a screen. See
Payment methods and mandates.
Run the scheduler and a queue worker
The package registers its own scheduled commands. Planning, collecting, renewing and ending subscriptions all happen there, so without cron nothing is ever charged:
# crontab
* * * * * cd /path-to-your-app && php artisan schedule:run >> /dev/null 2>&1
# a long-running worker for the queued notifications and listeners
php artisan queue:work
The sync queue is not supported outside local development. See
Scheduled commands.
Subscribe a customer
A customer subscribes to a plan (a Subscription) with a recurring payment
method. You need at least one active plan: create it in the management
dashboard (/billing/admin/subscriptions), in one of the admin companions, or
on the model (The Subscription plan).
On a fresh installation, php artisan larsasub:install --seed adds two
example plans.
Through the bundled dashboard
Send the signed-in customer to /billing/dashboard (route
larsasub.my-subscription.view). There they pick a plan and a payment method,
accept your terms when you have configured them, and go to Mollie's checkout.
Afterwards Mollie sends them back to the dashboard, and the subscription
activates when the webhook reports the first payment as paid. The URL is
standalone.url.subscription-dashboard; see
Standalone dashboards.
From your own code
A headless host asks the payment provider to create the subscription and start the first payment:
use PetersDevelopment\LarsaSub\Models\PaymentMethod;
use PetersDevelopment\LarsaSub\Models\Subscription;
use PetersDevelopment\LarsaSub\PaymentProviders\PaymentProviderFactory;
$plan = Subscription::query()->isEnabled()->findOrFail($request->input('plan_id'));
$method = PaymentMethod::query()
->isEnabled()
->isRecurring()
->where('provider_id', 'directdebit')
->firstOrFail();
return PaymentProviderFactory::make(config('larsasub.provider'))
->createSubscription($request->user(), $plan, $method);
createSubscription() returns a RedirectResponse to Mollie's checkout. The
subscription stays pending until the webhook reports the first payment as
paid; then it activates, and the mandate that payment created collects every
later charge. A plan that collects nothing now follows
subscription.free_plan_mandate instead of going to a checkout.
- Call it outside your own database transaction. It makes the provider call itself, and LarsaSub never holds a lock across one; see Wrapping a LarsaSub call in your own transaction.
- A refusal throws
Exceptions\BillableSubscriptionServiceExceptionwith a message you can show the customer. When the first payment cannot be started, the new subscription is deactivated and removed before it throws. - Consent is yours. The terms gate lives in the bundled dashboard only; a headless host records acceptance itself (Terms acceptance).
- In Connect mode pick the method with
PaymentMethod::query()->isRecurring()->availableForTenant($tenant).
Subscribing on a mandate the customer already has, starting after a trial and cost lines on the first charge are in Subscriptions.
Check access
$subscription = $user->activeSubscription; // ?BillableSubscription
if ($subscription !== null) {
// the user has a running subscription
}
$user->hasFeature('premium_support'); // bool
$limit = $user->featureValue('api_calls') ?? 100; // null without an active subscription
$subscription?->paidThrough(); // ?CarbonImmutable: last day paid for
Read activeSubscription as a property. With parentheses,
activeSubscription() is the relation, an object that is always truthy.
Whether a customer keeps access when paidThrough() passes is your decision;
the package never cuts access itself
(Paid through).
Across all subscribers, chain the scopes on a query:
use PetersDevelopment\LarsaSub\Models\BillableSubscription;
BillableSubscription::query()->isActive()->get();
BillableSubscription::query()->isChargeable()->get();
From a signed-in user to the model that pays:
use PetersDevelopment\LarsaSub\Helpers\BillableResolver;
$owner = BillableResolver::resolve($user); // the user, or its team in indirect mode
$owner = BillableResolver::fromRequest($request);
$isDirect = BillableResolver::isDirect(); // true when the user itself pays
In explicit mode BillableResolver::resolve() throws unless
billable.owner_resolver is configured; see
Explicit mode.
Billing a team or a company
By default the user pays (direct billing). When a related model pays, so that
several users share one subscription, point billable.model at it and name
the relation on the user:
// config/larsasub.php
'billable' => [
'model' => \App\Models\Team::class,
'relation' => 'team', // $user->team() returns the team that pays
],
The team takes the contract and the traits the user had above:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use PetersDevelopment\LarsaSub\Contracts\BillableModel;
use PetersDevelopment\LarsaSub\PaymentProviders\Mollie\Traits\IsMollieBillableModel;
use PetersDevelopment\LarsaSub\Traits\HasSubscriptions;
use PetersDevelopment\LarsaSub\Traits\IsBillableModel;
class Team extends Model implements BillableModel
{
use HasSubscriptions, IsBillableModel, IsMollieBillableModel;
public function getBillableNameAttribute(): string
{
return $this->name;
}
public function getBillableEmailAttribute(): string
{
return $this->contact_email;
}
public function getBillableIdentifierAttribute(): string
{
return (string) $this->id;
}
// Optional: who receives the team's billing mail
public function getNotifiableUsers(): Collection
{
return $this->users;
}
}
Feature checks then go through the team: $user->team->hasFeature('premium_support').
When one user acts for several billables, use explicit mode instead; see
Billable models.
Mollie Connect
With Mollie Connect every tenant of your platform connects its own Mollie account, and its customers' payments land there, with an optional application fee for you. Connect collects payments on behalf of others, which requires the Platform license tier. Declare it next to the switch: without the declaration the package refuses to boot. Nothing is verified over the network and nothing is sent anywhere; see larsasub.eu/pricing.
LARSASUB_LICENSE_TIER=platform
LARSASUB_CONNECT_ENABLED=true
LARSASUB_CONNECT_TENANT_RELATION=organization
LARSASUB_CONNECT_TENANT_LABEL=organization
LARSASUB_CONNECT_TENANT_NAME_ATTRIBUTE=name
LARSASUB_CONNECT_TENANT_OWNER_ATTRIBUTE=owner_id
# Mollie OAuth app (Mollie Dashboard → Developers → OAuth apps)
MOLLIE_CONNECT_CLIENT_ID=app_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
MOLLIE_CONNECT_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Your own Mollie organization: no application fee is charged on it
MOLLIE_ORGANIZATION_ID=org_xxxxxxxx
The same settings in config/larsasub.php:
'license' => [
'tier' => 'platform', // required for Connect
],
'billable' => [
'model' => \App\Models\User::class, // the tenants' customers pay
],
'connect' => [
'enabled' => true,
'tenant_relation' => 'organization', // $user->organization() returns the tenant
'tenant_label' => 'organization', // the word used in the UI and in mails
'tenant_name_attribute' => 'name', // $tenant->name is shown in customer mails
'tenant_owner_attribute' => 'owner_id', // the tenant's owner may manage the Mollie connection
],
php artisan larsasub:license shows the declared tier and whether the enabled
features fit it. See Mollie Connect for the scopes, website
profiles, disconnecting a tenant and settlements.
Application fees
Charge a platform fee on every tenant payment:
LARSASUB_CONNECT_FEE_ENABLED=true
LARSASUB_CONNECT_FEE_TYPE=percentage # percentage or fixed
LARSASUB_CONNECT_FEE_VALUE=10 # 10% or €10,00
LARSASUB_CONNECT_FEE_DESCRIPTION="Platform fee"
A plan can override the fee with its fee_type and fee_value fields. No fee
is charged when the platform model itself pays, or when the tenant's Mollie
organization is your own (connect.platform_organization_id). See
Application fees.
The tenant OAuth flow
- The tenant owner clicks "Connect Mollie" on the tenant connect dashboard
(
/billing/admin/connect,standalone.url.tenant-connect-dashboard). - LarsaSub redirects to Mollie's authorization page.
- The tenant authorizes your platform on its Mollie account.
- Mollie redirects back with an authorization code.
- LarsaSub exchanges the code for access and refresh tokens.
- The tokens are stored encrypted on the tenant's connected account
(
larsasub_mollie_connected_accounts). - The tenant can receive payments.
Tenant-scoped plans and customers
In Connect mode Mollie customers and mandates live per tenant, and plans can be the tenant's own or global:
use PetersDevelopment\LarsaSub\Models\Subscription;
// Created under the tenant's Mollie account, with the tenant's access token
$user->getMollieCustomer();
// The tenant's plans plus the global ones
$plans = Subscription::query()
->forTenant($tenant, includeGlobal: true)
->isEnabled()
->get();
Dual billing
When the platform bills its tenants itself and the tenants bill their own customers through Connect, name the tenant model as the platform's billable:
// config/larsasub.php
'billable' => [
'model' => \App\Models\User::class, // tenants' customers: tenant's Connect account
'platform_model' => \App\Models\Organization::class, // billed by the platform: your own Mollie key
],
Both models implement BillableModel and use the traits shown above.
MollieClientResolver routes each payment to the right account: payments of
the platform model on your own API key, those of the users on the tenant's
access token. See Dual billing.
The bundled dashboards
Twelve standalone pages ship with the package, each switchable under
standalone.dashboards and with its URL under standalone.url:
| Default URL | For | Shows |
|---|---|---|
/billing/dashboard |
Customer | The subscription: subscribe, change, cancel |
/billing/payments |
Customer | Payment history; paying a failed charge or an open single payment |
/billing/payment-methods |
Customer | Payment methods and mandates |
/billing/credit |
Customer | Credit balance and history |
/billing/profile |
Customer | Billing details: invoice profile, VAT number, country |
/billing/invoices |
Customer | Invoices (with invoicing on) |
/billing/admin/subscriptions |
Management | Plans |
/billing/admin/subscribers |
Management | Subscribers |
/billing/admin/charges |
Management | Charges |
/billing/admin/invoices |
Management | Invoices (with invoicing on) |
/billing/admin/connect |
Tenant owner | The Mollie connection (Connect mode only) |
/billing/admin/fees |
Tenant owner | Application fees (Connect mode only) |
In Connect mode the management pages are the tenant owner's view of the
tenant; in simple mode they are operator management, granted through
larsasub.policies.admin_role. See Standalone dashboards for
middleware, the guard and theming.
Commands you will use
# Check configuration, schema and the billable model
php artisan larsasub:doctor
# Show the declared license tier
php artisan larsasub:license
# Two example plans (fresh installation only: skipped on an existing one,
# so it cannot duplicate a live catalogue)
php artisan larsasub:install --seed
# The scheduled work, run by hand while you test
php artisan larsasub:plan-charges
php artisan larsasub:execute-charges
php artisan larsasub:notify-charges
php artisan larsasub:renew-billable-subscriptions
php artisan larsasub:announce-renewal-billable-subscriptions
php artisan larsasub:process-expired-credits
php artisan larsasub:notify-expiring-credits
# Send every mail with sample data (not registered in production)
php artisan larsasub:test-notifications --list
With scheduling.enabled off, the scheduled commands do nothing, also when
you run them by hand. See Artisan commands for the full list.
Next steps
- Configuration: every setting in
config/larsasub.php - Subscriptions: the lifecycle, changing, cancelling, renewing
- Recurring charges: collection, retries and what a customer may pay
- Credit: balances, expiry and how credit reaches a charge
- Invoicing and VAT: invoices, credit notes and what gets collected
- Mollie Connect: per-tenant accounts, OAuth, application fees, dual billing
- Standalone dashboards: the bundled pages, routing and theming
- Events and Notifications: hooking in and customizing
- Scheduled commands: the automation that runs your billing
- Public API: what you may depend on, and what is internal
- Compliance: SEPA, VAT, consumer rules and GDPR