Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mollie Billing Laravel Package

graystackit/laravel-mollie-billing

Batteries-included Mollie billing for Laravel with VAT/OSS compliance, wallet-based metered billing, coupons, scheduled plan changes, trials, an admin panel and a Livewire 4 customer portal, all keyed off a Billable contract.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimum viable wiring — do these in order; several are order-dependent:

  1. composer require graystackit/laravel-mollie-billing
  2. composer require livewire/flux-pro — not pulled in transitively; it's a commercial license you must own. The portal, checkout, and admin panel all hard-depend on it.
  3. Publish config + migrations: php artisan vendor:publish --tag=mollie-billing-config and --tag=mollie-billing-migrations.
  4. Before running migrations, edit config/mollie-billing.php and set billable_model, billable_key_type (uuid|ulid|int), and user_key_type. These drive the column type of every polymorphic FK (including bavix/laravel-wallet's wallets.holder_id, transactions.payable_id, transfers.*_id) — get them wrong and you'll be hand-ALTERing tables later.
  5. php artisan migrate
  6. Add HasBilling + implement Billable on your tenant model (e.g. Organization), not User. Provide getUsedBillingSeats() so seat-based metering has a number to read.
  7. Set .env: BILLING_MOLLIE_KEY, BILLING_BILLABLE_MODEL, BILLING_BILLABLE_KEY_TYPE, BILLING_USER_KEY_TYPE, BILLING_CURRENCY.
  8. Mount four route groups (see below) and register resolveBillableUsing + authUsing in AppServiceProvider::boot().
  9. Add your Tailwind @source line for vendor/graystackit/laravel-mollie-billing/resources/views/** (v4) or the content glob (v3). Without it the portal renders unstyled.
  10. Run php artisan billing:check-config — it checks the webhook route exists, feature_keys/allowed_addons/product group references, and syntax in both config files.

Where to look first:

  • config/mollie-billing.php + config/mollie-billing-plans.php — this pair is the product. Plans, features, addons, quotas, overage prices, currency, country list all live here.
  • docs/vat-handling.md — the country-mismatch reconciliation logic (three-way: declared vs payment vs IP) is the most surprising part of the package and fires on every recurring payment.
  • docs/audit.md — audit rows store a translation key plus raw values, not rendered text.

First use case to prove out: new Organization → hit the checkout route → complete first Mollie payment → confirm the webhook creates the local subscription → hit the portal /billing/plan and toggle a plan. If that round-trip works, VAT, mandates, and the billable model are all wired correctly.

Implementation Patterns

Route mounting is four distinct groups, with deliberate middleware boundaries. This is the core integration shape, not boilerplate:

// routes/web.php — portal, tenant-scoped
Route::middleware(['web', 'auth', 'tenant'])->group(fn () => MollieBilling::routes());

// routes/web.php — checkout, NOT tenant-scoped (tenant doesn't exist yet)
Route::middleware(['web', 'auth'])->group(fn () => MollieBilling::checkoutRoutes());

// routes/web.php — admin (auth only; auto-loaded unless you disable it)
Route::middleware(['web', 'auth'])->group(fn () => MollieBilling::adminRoutes());

// routes/api.php — webhook, NO `web` group (no session, no CSRF, no tenant)
MollieBilling::webhookRoutes();

Multi-tenant URL prefixes — mount MollieBilling::routes() inside your tenant prefix group but do not wrap it in your own ->name('tenant.'); package views call route('billing.*') by exact name. PropagateRouteDefaults (shipped) copies the active route params into URL::defaults, so links inside the portal carry the {organization:slug} automatically.

For non-request contexts (queued notifications, jobs, Mollie redirect URLs, emails), PropagateRouteDefaults can't help — register a global resolver:

MollieBilling::urlParametersUsing(
    fn (?Billable $billable) => $billable ? ['organization' => $billable->slug] : []
);

A model-level urlRouteParameters() override takes precedence over this if you need per-model logic.

Authorization is a callback, and it's load-bearing. Two closures in AppServiceProvider::boot():

MollieBilling::resolveBillableUsing(fn () => auth()->user()?->currentOrganization);

MollieBilling::authUsing(
    fn (Request $r, Billable $b): bool => $r->user()?->belongsToBillable($b) ?? false
);

Every cross-tenant defence in the package delegates to authUsing — portal middleware, invoice downloads, the checkout prefill gate. Implement it as an actual membership check, not auth()->check().

Feature gating — two flavours, pick per surface: @planFeature('api_access') in Blade, billing.feature:api_access middleware on routes. Feature keys must exist in mollie-billing-plans.php; billing:check-config flags unknown ones.

Metered billing flow — record usage against a usage-type (lookups are case-insensitive), let the wallet consume included quota, and the package charges overage at the configured price. Direct overage charging has retry and a past_due state; treat past_due as a first-class state in your UI, not an edge case.

Coupons — five types: SinglePayment, Recurring, Credits, TrialExtension, AccessGrant. AccessGrant is how you comp a full plan or addon-only access; don't build a parallel "free account" path. Promotion links are signed /promotion/{token} URLs via MollieBilling::promotionRoutes() (mount without tenant scope) for auto-apply.

Trial conversion — trial lives locally, converted to a Mollie subscription at end of trial. Plan changes are scheduled (prorata upgrades, end-of-period downgrades) — build admin/portal UIs around the scheduled state, not just the current plan.

Gotchas and Tips

The webhook route in the web group is the #1 footgun. Every payment the package creates embeds this URL; if you mount it inside web, the very first checkout throws RouteNotFoundException [billing.webhook] before it ever reaches Mollie. It must be session-less, CSRF-less, tenant-less, and the route must exist — billing:check-config verifies this, so run it after mounting routes.

billable_key_type / user_key_type are immutable after first migrate. The package rewrites bavix/laravel-wallet's default bigint FK columns to uuid/ulid based on these values. Changing them later means manually altering wallets.holder_id, transactions.payable_id, transfers.{from,to}_id, billing_country_mismatches.resolved_by_user_id, etc. Set them once, correctly.

The admin route double-registration trap. Admin routes auto-load from the service provider. If you also call MollieBilling::adminRoutes() under custom middleware without setting BILLING_AUTO_LOAD_ADMIN_ROUTES=false, the auto-loaded routes match the URI first and your middleware never runs. Either use the auto-load or disable it — not both.

authUsing is your cross-tenant gate, and the checkout query-param fallback depends on it. The checkout route sits outside tenant scope, so resolveBillableUsing returns null there. The package then falls back to reading the billable from query params (?organization=acme-corp, matching getRouteKeyName(); reserved keys back, plan, interval, redirect, token are skipped). That fallback adopts the resolved billable's billing data only if authUsing authorizes it. A permissive authUsing (e.g. fn() => auth()->check()) turns ?organization=<someone-else> into a disclosure of that tenant's address and VAT number.

flux-pro is a hard blocker, not a soft dependency. No Flux Pro, no checkout, no portal, no admin — with a commercial license required per app. Budget for it; it's not a composer require you can skip.

No compiled CSS/JS ships with the package. The shipped layouts call @vite(['resources/css/app.css', 'resources/js/app.js']), so your app must expose those entry points and your Tailwind build must scan the vendor views. Tailwind v4: @source "vendor/graystackit/laravel-mollie-billing/resources/views"; (or equivalent). Omit this and the portal silently renders unstyled. Publishing views (--tag=mollie-billing-views) helps only if your Tailwind already scans resources/views/** — the @source line is still needed for any unpublished views.

Country-mismatch reconciliation silently cancels. On each recurring payment, three country signals (user-declared, payment-derived, IP-derived) are compared. If the declared country matches neither signal, the subscription is set to cancel-at-period-end, the billable is emailed, and a dashboard modal offers self-correction (refund + reissue at the new rate). B2B billables with a VIES-validated VAT number bypass the check entirely (reverse-charge makes bank country fiscally irrelevant). Watch your logs for this; a cancellation you didn't initiate is probably this, not a Mollie problem. Admins can override to any ISO-2 code, but only against a written justification that lands in the audit trail.

Audit rows hold translation keys, not rendered strings. The timeline reads correctly in any locale because the package stores key + raw values. If you render the audit trail yourself, translate at display time — don't cache rendered rows.

Usage-type lookups are case-insensitive. Fine for ergonomics, but means api_calls, API_CALLS, and Api_Calls are the same bucket. Standardize constants on your side; don't rely on case to distinguish usage types.

billing:check-config is your pre-deploy gate. It checks both config files for syntax errors, broken references (unknown feature_keys, allowed_addons, product group), and the webhook route. Wire it into CI — config drift after adding a plan is easy to miss otherwise.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource