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 Cashier Mollie Laravel Package

mollie/laravel-cashier-mollie

Laravel Cashier for Mollie provides an expressive, fluent interface for Mollie payments and subscriptions in Laravel. Manage billing, invoices, refunds, and recurring payments with ease using familiar Cashier-style APIs.

Deep Wiki
Context7

Getting Started

1. Install and configure

composer require mollie/laravel-cashier-mollie
php artisan vendor:publish --provider="Mollie\Laravel\Cashier\CashierServiceProvider"

Set the API key (a test key starts with test_):

MOLLIE_KEY=test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Add the Billable trait to whatever model pays you (usually User):

use Mollie\Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

Publish and run the package migrations — they add mollie_customer_id, mollie_mandate_id, trial_ends_at and tax_percentage to users, plus subscriptions and order_items tables used for invoices.

php artisan vendor:publish --tag="cashier-migrations"
php artisan migrate

2. Register the webhook. Mollie is asynchronous: subscription renewals, mandate creation and payment status changes only become real when Mollie calls you back. Point the webhook URL in the Mollie dashboard (Settings → Website profiles → Webhooks) at the package's route, by default /webhook/mollie (check config('cashier.webhook')). If the route lives behind auth middleware, subscriptions will silently never activate.

3. Look first at: config/cashier.php (key, currency, locale, webhook path, invoice settings), then the Billable trait source, then Mollie\Laravel\Cashier\Subscription. Do not pattern-match against the Stripe Cashier docs — the two packages share a naming style, not an API.

First use case: a single monthly plan. Create the plan in the Mollie dashboard, then:

$checkout = $user->newSubscription('main', 'monthly-10-1.00')
    ->create($request->input('payment_method')); // e.g. 'creditcard', 'directdebit', 'ideal'

return $checkout; // RedirectResponse to Mollie's checkout

The user pays, Mollie creates a mandate, the webhook fires, and $user->subscribed('main') becomes true.

Implementation Patterns

Recurring vs one-off payment methods. This is the single most important modelling decision. Mollie subscriptions are mandate-based: the first payment must use a method that supports recurring charges (credit card, SEPA Direct Debit, PayPal, and a few others). iDEAL, Bancontact, Klarna, etc. are one-off only — you can take a payment with them but you cannot attach a subscription. Offer recurring methods on your subscription page and one-off methods on your top-up/store page.

Subscription lifecycle. Treat the local subscriptions table as a cache of Mollie state, updated by webhooks:

if ($user->subscribed('main')) { ... }
if ($user->subscription('main')->onTrial()) { ... }
$user->subscription('main')->cancel();      // at period end
$user->subscription('main')->cancelNow();
$user->subscription('main')->resume();
$user->subscription('main')->swap('monthly-20-1.00');

Guard your own gating logic with subscribed() / onTrial() rather than reading the table directly, and let the webhook controller (Mollie\Laravel\Cashier\Http\Controllers\WebhookController) own the transitions.

One-off charges and invoices. For one-off amounts, use the billable model's charge/invoice helpers, and remember Mollie wants amounts as decimal strings with the right currency — let the package's formatter do it:

$user->charge(10.00, 'ideal', ['description' => 'Credits pack']);
$user->invoiceFor('Credits pack', 10.00);

$user->invoices() returns Mollie\Laravel\Cashier\Invoice objects; each invoice is backed by a Mollie order and its order_items rows. Expose a download route as GET /invoices/{id} returning $user->downloadInvoice($id) behind your own policy, and keep the route name in config so emails and the customer portal can link to it.

Hooking into webhooks. Override the package's webhook controller instead of writing your own endpoint — extend it and implement the handle... methods you care about, keeping the parent call:

class WebhookController extends \Mollie\Laravel\Cashier\Http\Controllers\WebhookController
{
    protected function handlePaymentPaid(array $payload): Response
    {
        // top up credits, send receipts, notify Slack ...
        return parent::handlePaymentPaid($payload);
    }
}

Register the route to your subclass. The package dispatches events (WebhookReceived, WebhookHandled, plus subscription/payment events) which are often the cleanest integration point — listen for a paid payment and keep business logic out of the controller.

Trials and no-card signup. $user->newSubscription('main', 'monthly-10-1.00')->trialDays(14)->create() with no payment method works because no first payment is needed; trial_ends_at on the user / subscription records the window. Convert before the trial ends by sending the user through the mandate flow, otherwise Mollie can't charge and the subscription stalls.

Multi-currency. Call Cashier::useCurrency('EUR') in a service provider (or per-request for localised pricing) before any charge. Changing currency mid-subscription will break renewals, since the plan in Mollie is fixed in the profile's settlement currency.

Gotchas and Tips

  • The webhook is the source of truth, not the return URL. Users can close the tab, and Mollie may redirect before settling. Never grant access on the redirectUrl return; grant it in the webhook handler. Make the endpoint idempotent — Mollie retries, and the same payment payload can arrive more than once.
  • No signature verification. The package verifies webhooks by re-fetching the payment/order from the Mollie API, so the endpoint must be public, CSRF-exempt, fast (< a few seconds, otherwise Mollie marks it failed), and must return 2xx. A 419 or a 302 to /login from a middleware mistake is the number one cause of "subscriptions never activate".
  • Amounts are integers in cents in the Mollie API but decimals in the package API. Mixing the two produces 100× overcharges or API validation errors. Always use Cashier::formatAmount() / Cashier::usesCurrency() rather than hand-rolling number_format. Write a test that asserts the amount sent to Mollie.
  • Local testing needs a tunnel. Use valet share / ngrok / Expose and set the webhook URL to the tunnel. Alternatively, write a small artisan command that fetches a payment ID from Mollie and dispatches the handler locally — far easier to debug than round-tripping through the dashboard.
  • MOLLIE_KEY is read at boot by the singleton client. After editing .env, run php artisan config:clear; in production with config caching, config:cache after every key change. A stale test key in production is a classic.
  • Don't copy Stripe Cashier snippets. Cashier::createCustomer, ->invoices() signatures, ->applyCoupon() and webhook payload shapes differ. The package's own test suite is the best reference for exact method signatures — read it before assuming a Cashier Stripe method exists.
  • Testing. Bind a fake/mock of the Mollie API client into the container in your test case, or Http::fake(['api.mollie.com/*' => Http::response($fixture)]), so subscription tests don't hit the network. Test the webhook by POSTing the payload your handler expects and asserting a 200.
  • Extension points: the Billable trait (override mollieCustomer() or the customer-creation hook to add metadata), the cashier config file, and the webhook controller subclass. For anything the package doesn't model — coupons, metered usage, proration — you're on your own; keep that logic in your app and reconcile against Mollie's subscription/discount primitives rather than forking the package.
  • Pin the version. The library tracks Laravel releases closely; check the composer.json constraint against your Laravel and PHP versions before upgrading, and run a webhook replay in staging after each major bump.
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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource