Laravel + Mollie: betaalflows buiten Shopify en Magento
Terug naar blog

Laravel + Mollie: betaalflows buiten Shopify en Magento

AuthorRuthger Idema
22 april 202610 min leestijd

Mollie verwerkt miljarden aan betalingen per jaar. Voor maatwerk betaalflows buiten het standaard Shopify/Magento-pad — subscription modellen, split payments, B2B-facturatie — is Laravel + Mollie de juiste combinatie.

Laravel + Mollie: betaalflows buiten Shopify en Magento

Mollie verwerkte in 2023 meer dan €30 miljard aan betalingen. Voor standaard webshops is de Shopify of Magento plugin voldoende. Maar zodra je afwijkt van het standaard checkout-pad — abonnementen, split payments, B2B-facturatie op 30 dagen, klantspecifieke betaalportalen — zijn de standaard plugins te beperkend.

Laravel + Mollie geeft je volledige controle over de betaalflow. Dit is hoe je het correct implementeert.

Wat je leert in dit artikel

  • Mollie API integratie in Laravel via de officiële SDK
  • Payment methods dynamisch ophalen en tonen
  • Webhooks correct verwerken en beveiligen
  • Refunds programmatisch uitvoeren
  • Recurring payments en subscription-logica bouwen

Mollie SDK installeren en configureren

Mollie heeft een officiële PHP SDK die uitstekend werkt met Laravel.

bash
composer require mollie/mollie-api-php

Wij wikkelen de SDK in een eigen service class. Zo hou je de Mollie-logica op één plek en kun je makkelijk testen via mocks.

php
// app/Services/MollieService.php
use Mollie\Api\MollieApiClient;

class MollieService
{
    private MollieApiClient $mollie;

    public function __construct()
    {
        $this->mollie = new MollieApiClient();
        $this->mollie->setApiKey(config('services.mollie.api_key'));
    }
}

Sla de API key op in .env. Gebruik een test-key tijdens development — Mollie heeft een uitgebreide test-omgeving met gesimuleerde betaalstatussen.

Payment methods: dynamisch ophalen

Mollie ondersteunt iDEAL, creditcard, Bancontact, SEPA, PayPal, Klarna en meer. Welke methoden beschikbaar zijn, hangt af van je Mollie-account en het betaalbedrag. Haal ze dynamisch op — niet hardcoden.

php
public function getBeschikbareMethoden(int $bedragInCenten, string $valuta = 'EUR'): array
{
    $methoden = $this->mollie->methods->allActive([
        'amount' => [
            'value'    => number_format($bedragInCenten / 100, 2, '.', ''),
            'currency' => $valuta,
        ],
        'locale'       => 'nl_NL',
        'sequenceType' => 'oneoff',
    ]);

    return collect($methoden)
        ->map(fn($methode) => [
            'id'          => $methode->id,
            'description' => $methode->description,
            'image'       => $methode->image->size2x,
            'issuers'     => $methode->issuers ?? [],
        ])
        ->toArray();
}

Voor iDEAL heb je de issuers nodig: de lijst met banken. Die zitten direct in het methode-object als je includeWallets=1 of de issuers embed meegeeft.

Betaling aanmaken

Een betaling aanmaken in Mollie is één API-call. Het kritieke punt is de webhookUrl — die moet bereikbaar zijn vanuit het internet, ook in testomgevingen (gebruik ngrok of een staging URL).

php
public function betalingAanmaken(Order $order, string $methode): string
{
    $betaling = $this->mollie->payments->create([
        'amount' => [
            'value'    => number_format($order->total_amount / 100, 2, '.', ''),
            'currency' => 'EUR',
        ],
        'description' => "Order #{$order->order_number}",
        'redirectUrl' => route('checkout.bedankt', $order->uuid),
        'webhookUrl'  => route('webhooks.mollie'),
        'method'      => $methode,
        'metadata'    => [
            'order_id'     => $order->id,
            'order_number' => $order->order_number,
        ],
    ]);

    // Sla het Mollie payment ID op voor latere referentie
    $order->update(['mollie_payment_id' => $betaling->id]);

    return $betaling->getCheckoutUrl();
}

Stuur de klant door naar $betaling->getCheckoutUrl(). Mollie handelt de betaalinterface af en stuurt de klant terug naar je redirectUrl.

Webhooks correct verwerken

De redirectUrl is niet betrouwbaar voor betaalstatus. Een klant kan de tab sluiten na betaling. Gebruik altijd de webhook voor de definitieve statuswijziging.

php
// app/Http/Controllers/MollieWebhookController.php
class MollieWebhookController extends Controller
{
    public function handle(Request $request, MollieService $mollie): Response
    {
        $paymentId = $request->input('id');

        if (! $paymentId) {
            return response('Geen payment ID', 400);
        }

        // Haal de actuele betaalstatus op via de API — vertrouw nooit de webhook-body
        $betaling = $mollie->getBetaling($paymentId);

        $order = Order::where('mollie_payment_id', $paymentId)->first();

        if (! $order) {
            // Mollie stuurt ook webhooks voor testbetalingen
            return response('OK', 200);
        }

        match ($betaling->status) {
            'paid'     => VerwerkBetaling::dispatch($order->id),
            'failed'   => BetalingMislukt::dispatch($order->id),
            'canceled' => BetalingGeannuleerd::dispatch($order->id),
            'expired'  => BetalingVerlopen::dispatch($order->id),
            default    => null,
        };

        // Altijd 200 teruggeven — anders herprobeert Mollie de webhook
        return response('OK', 200);
    }
}

Twee punten zijn hier kritiek. Vertrouw nooit de webhook-body voor de status — haal altijd de actuele status op via de API. En geef altijd een 200 terug. Als je webhook faalt, herprobeert Mollie tot 10 keer in 24 uur.

Webhook route buiten CSRF-bescherming

Mollie's webhook is een externe POST-request. Laravel's CSRF-middleware blokkeert dit standaard.

php
// app/Http/Middleware/VerifyCsrfToken.php
protected $except = [
    'webhooks/mollie',
];

Of gebruik de web-middleware stack niet voor webhook routes:

php
// routes/api.php
Route::post('/webhooks/mollie', [MollieWebhookController::class, 'handle'])
    ->name('webhooks.mollie');

Refunds programmatisch uitvoeren

Mollie maakt refunds eenvoudig. Je kunt een volledige of gedeeltelijke terugbetaling uitvoeren.

php
public function terugbetaling(Order $order, int $bedragInCenten): void
{
    $betaling = $this->mollie->payments->get($order->mollie_payment_id);

    if (! $betaling->canBeRefunded()) {
        throw new BetalingKanNietWordenTerugbetaald(
            "Betaling {$order->mollie_payment_id} kan niet worden terugbetaald."
        );
    }

    $refund = $betaling->refund([
        'amount' => [
            'value'    => number_format($bedragInCenten / 100, 2, '.', ''),
            'currency' => 'EUR',
        ],
        'description' => "Terugbetaling order #{$order->order_number}",
    ]);

    $order->update([
        'mollie_refund_id'  => $refund->id,
        'refund_status'     => $refund->status,
        'refunded_at'       => now(),
    ]);
}

Recurring payments en subscriptions

Mollie ondersteunt recurring payments via Mandates en Subscriptions. Dit is interessant voor SaaS-producten, abonnementen of periodieke B2B-bestellingen.

De flow werkt in drie stappen. Eerste betaling met sequenceType: first. Daarna een mandate aanmaken. Vervolgens automatische incasso's via sequenceType: recurring.

php
// Eerste betaling — klant geeft mandate via iDEAL of creditcard
public function eersteBetalingMetMandate(Customer $customer, int $bedragInCenten): string
{
    $betaling = $this->mollie->payments->create([
        'amount'       => ['value' => number_format($bedragInCenten / 100, 2, '.', ''), 'currency' => 'EUR'],
        'description'  => 'Eerste betaling — abonnement activeren',
        'redirectUrl'  => route('abonnement.bevestigd'),
        'webhookUrl'   => route('webhooks.mollie'),
        'sequenceType' => 'first',
        'customerId'   => $customer->mollie_customer_id,
        'method'       => 'ideal',
    ]);

    return $betaling->getCheckoutUrl();
}

// Automatische incasso na mandate
public function periodiekeIncasso(Customer $customer, int $bedragInCenten, string $beschrijving): void
{
    $this->mollie->payments->createFor($this->getMollieKlant($customer), [
        'amount'       => ['value' => number_format($bedragInCenten / 100, 2, '.', ''), 'currency' => 'EUR'],
        'description'  => $beschrijving,
        'webhookUrl'   => route('webhooks.mollie'),
        'sequenceType' => 'recurring',
    ]);
}

Veelgemaakte fouten

Betaalstatus ophalen uit redirect URL. De redirect URL bevat geen status. Een klant kan de URL manueel openen. Haal de status altijd op via de webhook of via een expliciete API-call. Webhook niet beveiligen. Mollie heeft geen HMAC-signing op webhooks. Beveilig je webhook door de status altijd op te halen via de API in plaats van de webhook-body te vertrouwen. Mollie customer ID niet opslaan. Voor recurring payments heb je een Mollie customer ID nodig. Sla het op bij klantregistratie. Bedragen als float. Gebruik altijd integers (centen) intern. Converteer alleen naar string met exact twee decimalen bij de API-call. 19.999999 afronden naar 20.00 geeft problemen.

Conclusie

Laravel + Mollie is een krachtige combinatie voor betaalflows buiten de standaard checkout. De SDK is goed onderhouden, de webhooks zijn betrouwbaar en de recurring payment-functionaliteit is productieklaar.

Het verschil zit in de details: webhooks correct beveiligen, bedragen als integers bewaren, altijd de status ophalen via de API. Die gewoonten scheiding correcte en incorrecte implementaties.

De Mollie API documentatie en de Mollie Laravel package zijn de referenties voor de implementatie. Voor webhook-beveiliging, zie ook de Laravel webhook-documentatie.

Gerelateerde artikelen:

Wil je een betaalflow bouwen buiten Shopify of Magento? Bekijk onze Laravel-diensten of neem contact op.

Ruthger Idema

Geschreven door Ruthger Idema

15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.

Meer over ons team →
Deel dit artikel:

Wil je jouw e-commerce naar het volgende niveau?

Plan een vrijblijvend gesprek met onze experts over Magento, Shopify of Laravel maatwerk.

Plan een Tech Check