sander-van-hooft/laravel-payable-redirect-mollie
A Laravel package that adds Mollie payment redirect support to the Laravel Payable ecosystem, helping you wire up Mollie checkout redirects and payment flows with less boilerplate.
Install the package. It lives in the sander-van-hooft/laravel-payable-redirect-mollie namespace and is a companion driver for sander-van-hooft/laravel-payable, so make sure that core package is also required at a compatible version.
composer require sander-van-hooft/laravel-payable-redirect-mollie
Publish config and migrations.
php artisan vendor:publish --provider="SanderVanHooft\Payable\PayableServiceProvider"
php artisan migrate
Check config/payable.php (or whatever the published filename is) for the Mollie key, currency, and return-route settings. If both the core package and this driver ship migrations, only publish one payments table to avoid duplicate-table errors.
Configure Mollie. Set MOLLIE_KEY in .env. For local/dev work use a test_... key, not a live live_... key — the test_ prefix is the sandbox switch, and it is easy to leave a live key in .env during testing.
Mark a model as payable by adding the SanderVanHooft\Payable\Traits\Payable (or PayableRedirect-equivalent) trait. This gives the model a polymorphic payments relation.
use SanderVanHooft\Payable\Traits\Payable;
class Order extends Model
{
use Payable;
}
First use case — pay for an order with a redirect.
Route::get('/orders/{order}/pay', function (Order $order) {
return $order->payment()
->create(4999, 'Order #'.$order->id) // amount in cents
->redirect();
});
The redirect() response is a Mollie checkout URL. After the customer pays, Mollie redirects back to the package's return route, which reads the payment status and updates the local Payment record.
Where to look first. Read the driver's routes/*.php, the return controller, and the Payment model. Those three files are where 90% of behavior lives, and you'll need to know them to customize return handling or status mapping.
The core idea is that the trait makes any Eloquent model payable via a morph relation. Use this for orders, invoices, subscriptions, tickets — anything with a single settlement. Keep the business model (Order) separate from the Payment record; don't try to jam payment state back into Order beyond convenience accessors like $order->payments()->where('status', 'paid')->exists().
The idiomatic flow is:
Payment row with status = pending and a Mollie ID.paid, mark the order fulfilled; on failed/canceled, let the user retry.Wrap steps 3–4 in your own listener or extend the return controller, because the package's default controller is intentionally minimal.
A "pay" route should be safe to refresh. Either:
pending payment for the payable model and reuse its checkout URL before creating a new one.$payment = $order->payments()->where('status', 'open')->latest()->first()
?? $order->payment()->create($order->total_cents, $order->description);
return $payment->redirect();
The package (as of 2018) leans on the browser redirect, which is fragile — the customer can close the tab before returning. For anything production-grade, add a webhook handler or a scheduled reconciliation job that queries Mollie for each pending payment and re-derives status. Use the same mapping logic in both paths so a status is never set in two divergent ways.
Because payments() is a morph relation, the same payments table serves many payables. In reports, join via payable_type + payable_id carefully; payable_type is a full class name, so renaming or namespacing a model silently orphans rows. Add a morphMap in a service provider to stabilize this:
Relation::morphMap([
'order' => \App\Models\Order::class,
'invoice' => \App\Models\Invoice::class,
]);
Payment in the container so you can add paid_at, provider_refund_id, etc.payable model class are typically all in config/payable.php (or payable-mollie.php in some versions).Use Mollie's sandbox or mock the client in Laravel tests:
Mollie\Api\MollieApiClient into the container.test_ mode with a payment ID you control.Payment row and what you sent to Mollie (amount, description, redirect URL) to catch cents-vs-euros bugs early.It is a 2018 package. Last release is Feb 2018, 21 stars, zero dependents. Assume Laravel 5.5–5.8 compatibility. Check the composer constraints before installing on Laravel 9/10/11 — you'll likely need composer require with --ignore-platform-reqs or a fork. Do not assume it works on current Laravel without an audit.
Two packages, one relation. laravel-payable-redirect-mollie is a driver for sander-van-hooft/laravel-payable. Version mismatches between the two are the most common install failure. Pin both and read the driver's composer.json.
Amounts are cents (integer). 4999 means €49.99, not €4999. Every "off by 100x" refund bug in packages like this comes from sending a decimal through an integer column or vice versa. Cast in your model accessor, not at the call site.
Currency is a config, not a parameter. Mollie infers currency from your account, but a mismatch between your configured currency and the amount can produce cryptic errors. For non-EUR shops, confirm config/payable.php (or the payment create() call) accepts a currency override before going live.
Return URL vs. APP_URL. The package builds the return URL from routing and APP_URL. Behind a proxy, custom domain, or in a subdirectory, the wrong scheme/host breaks the redirect. Set APP_URL, trust the proxy (TrustProxies), and verify with php artisan route:list that the return route exists with the expected name and methods.
Browser redirect ≠ payment confirmation. A user closing the tab leaves a pending row. Never fulfill an order from a redirect() call or from the client. Confirm via Mollie's API (webhook or scheduled poll). Treat paid status server-side as the only trigger for fulfillment.
Sandbox vs. live key is a one-character mistake. test_... and live_... differ only in prefix; swapping .env files is the #1 way to accidentally charge real money in staging. Add a boot-time guard that throws if APP_ENV !== 'production' and str_starts_with(config('payable.mollie_key'), 'live_').
Route collisions. The package registers its own routes; if you also define a /payable/* path or a payments route group, Laravel may pick the wrong one. Check order in routes/web.php and use php artisan route:list | grep payable.
Migrations may clash with the core laravel-payable migrations if both are published. Publish once, diff the files, and delete duplicates.
Debugging checklist:
storage/logs/laravel.log — package exceptions are usually there.php artisan route:list | grep payable — does the return route exist?Payment row — status, mollie_id, payable_type, payable_id.https://www.mollie.com/... URL?Extension point to reach for first: overriding the return controller. It's smaller and safer than monkey-patching the package, and it lets you keep the driver for redirect creation while owning the post-return business logic.
Long-term viability. With zero dependents and a seven-year-old codebase, treat this as glue you may fork and maintain yourself. Budget time to either upgrade it (Mollie API v2, Laravel 10/11 support) or replace it with direct mollie/mollie-api-php calls plus a thin Payment model of your own.
How can I help you explore Laravel packages today?