mollie/laravel-mollie
Laravel integration for Mollie payments. Easily set up the Mollie API client via configuration and service container, handle payments, refunds, and subscriptions, and keep your checkout flow clean with a simple, idiomatic Laravel package.
Installation
composer require mollie/laravel-mollie
Publish the config file:
php artisan vendor:publish --provider="Mollie\LaravelMollie\MollieServiceProvider" --tag="mollie-config"
Configuration
Add your Mollie API key to .env:
MOLLIE_KEY=test_XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Set your preferred locale (e.g., nl_NL, en_US) in config/mollie.php.
First Use Case: Create a Payment
use Mollie\LaravelMollie\Facades\Mollie;
$payment = Mollie::payments()->create([
'amount' => [
'currency' => 'EUR',
'value' => '10.00',
],
'description' => 'Order #12345',
'method' => 'ideal', // or 'creditcard', 'paypal', etc.
'redirectUrl' => route('payment.success'),
]);
return redirect()->to($payment->getCheckoutUrl());
Webhook Setup
Add the Mollie webhook endpoint to your routes/web.php:
Route::post('/mollie/webhook', [MollieWebhookController::class, 'handleWebhook']);
Register the controller in app/Providers/RouteServiceProvider.php:
public function boot()
{
$this->routes(function () {
Route::post('/mollie/webhook', [MollieWebhookController::class, 'handleWebhook'])
->middleware('signed'); // Optional: Add CSRF protection
});
}
Dynamic Payments Use a service class to abstract payment logic:
class PaymentService {
public function createOrderPayment(Order $order)
{
return Mollie::payments()->create([
'amount' => [
'currency' => $order->currency,
'value' => $order->total,
],
'description' => 'Order #' . $order->id,
'metadata' => ['order_id' => $order->id],
'webhookUrl' => route('mollie.webhook'),
]);
}
}
Subscription Handling For recurring payments:
$subscription = Mollie::subscriptions()->create([
'amount' => ['currency' => 'EUR', 'value' => '9.99'],
'interval' => 'month',
'times' => null, // Unlimited
'metadata' => ['customer_id' => $user->id],
'webhookUrl' => route('mollie.webhook'),
]);
Event-Driven Logic Handle webhooks in a dedicated controller:
class MollieWebhookController extends Controller {
public function handleWebhook(Request $request)
{
$event = Mollie::webhooks()->handle($request->getContent());
switch ($event->type) {
case 'payment.authorized':
$this->handleAuthorizedPayment($event->data->id);
break;
case 'payment.paid':
$this->fulfillOrder($event->data->id);
break;
case 'payment.failed':
$this->handleFailedPayment($event->data->id);
break;
}
return response()->json(['status' => 'success']);
}
}
Idempotency
Use the idempotencyKey in API calls to avoid duplicate processing:
$payment = Mollie::payments()->create([
'amount' => ['currency' => 'EUR', 'value' => '10.00'],
'idempotencyKey' => 'unique-key-' . Str::uuid(),
]);
config/services.php):
'mollie' => [
'client_id' => env('MOLLIE_CONNECT_CLIENT_ID'),
'client_secret' => env('MOLLIE_CONNECT_CLIENT_SECRET'),
'redirect' => env('MOLLIE_CONNECT_REDIRECT_URI'),
],
Use in a controller:
public function redirectToMollieConnect()
{
return Socialite::driver('mollie')->redirect();
}
public function handleMollieConnectCallback()
{
$user = Socialite::driver('mollie')->user();
// Attach user data to your app's user model
}
Refund a Payment
$payment = Mollie::payments()->get('p_XXXXXXXXXXXXXXXXXXXXXXXXXXXX');
$refund = $payment->refunds()->create([
'amount' => ['currency' => 'EUR', 'value' => '5.00'],
'description' => 'Partial refund',
]);
Capture a Payment
$payment = Mollie::payments()->get('p_XXXXXXXXXXXXXXXXXXXXXXXXXXXX');
$payment->capture();
test_ API keys) and test cards (e.g., 4242 4242 4242 4242).$this->post('/mollie/webhook', $webhookPayload)
->assertStatus(200);
config/mollie.php:
'debug' => env('APP_ENV') === 'local',
event(new PaymentProcessed($payment));
Mollie::setLocale($user->locale); // e.g., 'nl_NL'
try {
$payment = Mollie::payments()->create([...]);
} catch (\Mollie\Api\Exceptions\ApiException $e) {
Log::error('Mollie API Error: ' . $e->getMessage());
return back()->with('error', 'Payment failed. Please try again.');
}
Issue: Mollie webhooks must be verified using the id and signature headers.
Fix: Use the built-in handle() method, which automatically verifies the webhook:
$event = Mollie::webhooks()->handle($request->getContent());
Manual Verification (if needed):
$isValid = Mollie::webhooks()->verify($request->getContent(), $request->header('X-Mollie-Signature'));
idempotencyKey can lead to duplicate payments.
Fix: Always include a unique key (e.g., order ID or UUID) for critical operations.number_format or bcdiv for amounts:
$amount = number_format($order->total, 2, '.', '');
ProcessPayment::dispatch($event->data->id)->onQueue('mollie');
payments.read):
$user = Socialite::driver('mollie')->scopes(['payments.read'])->user();
'debug' => true in config/mollie.php to log API requests/responses.dd() to inspect raw responses:
$payment = Mollie::payments()->create([...]);
dd($payment->toArray());
How can I help you explore Laravel packages today?