ruudk/payment-mollie-bundle
A Symfony bundle that adds a Mollie payment gateway to the ruudk/payment component, letting you create and handle Mollie payments and webhooks through a clean service-based API with minimal configuration.
What you're actually installing. This bundle is a thin adapter between JMSPaymentCoreBundle and the Mollie API. It ships a JMS payment plugin (tagged payment.plugin), a small return/notify controller, and DI config. You will spend most of your day in JMSPaymentCore's PaymentInstruction lifecycle, not in this bundle.
Minimal setup
composer require ruudk/payment-mollie-bundle
Be aware: the package is archived (last tag 2019), so Composer will pull old Symfony/Doctrine/Mollie-client constraints. Pin versions and expect conflicts on modern stacks.AppKernel:
new JMS\Payment\CoreBundle\JMSPaymentCoreBundle(),
new Ruudk\Payment\MollieBundle\RuudkPaymentMollieBundle(),
test_... for dev, live_... for prod) under ruudk_payment_mollie.api_key. Confirm the exact key name in DependencyInjection/Configuration.php — this is the first file to read.payment.plugin_controller options) — see its docs; this bundle assumes it's already working.Where to look first
DependencyInjection/Configuration.php — the only real config surface.Plugin/*Plugin.php — read getName() to get the exact payment-system string your PaymentInstruction must use (likely mollie_ideal; verify, don't guess), and see exactly which Mollie API calls are made.Resources/config/services.xml / routing.yml — service IDs, tags, route names.PaymentPluginController and OffsitePluginInterface — the contract this bundle implements.First use case: a one-off iDEAL payment. Create a PaymentInstruction (amount, EUR, the plugin's payment-system name), persist/flush it, then call payment.plugin_controller->approve($instruction, $amount). If $result->getResult() === Result::STATUS_SUCCESS, return the redirect from $result->getResponse() to send the user to Mollie. When Mollie redirects back, the bundle's controller calls complete() on the plugin controller; from there you inspect the instruction/payment state and fulfil the order. That start → approve → redirect → return → complete loop is the entire happy path.
Keep order state separate from payment state. Your Order entity should have its own status (pending, paid, failed, refunded). JMSPaymentCore tracks ApprovedAmount, DepositedAmount, and ReversingAmount on the instruction — use those for reconciliation, but never derive "the customer has paid" from ApprovedAmount alone. Only flip the order to paid after complete() plus a server-side status check against Mollie.
Two controllers, one flow. A start action (creates instruction, calls approve, redirects) and a return action (looks up the instruction by ID from session or a signed query param, calls complete(), verifies amount+currency match the order, then fulfils). Put the return URL behind your own route so you control error handling; the bundle's own controller is a convenience, not a policy.
Guard the return. Treat the browser redirect as a hint, not proof. In return, re-fetch the payment from Mollie (or rely on complete() doing so) and compare amount, currency, and the Mollie payment ID you stored at approve() time. If it doesn't match, mark the instruction failed and log.
Use the Result object properly. approve()/complete() return a Result; check getResult() against the STATUS_* constants and branch on getResponse() (offsite redirect) vs getPayment(). Don't assume STATUS_SUCCESS means "done" — for offsite plugins it often means "redirect the user".
Testing strategy. Because the plugin is small, stub the Mollie client or replace the plugin service (keep the payment.plugin tag) with a fake. Assert transitions: instruction created → approved → completed → deposited, and the failure path when Mollie returns failed/canceled/expired. Most of your test budget belongs to the order state machine, not to Mollie.
Refunds and reconciliation. If the plugin supports reverseDeposit, wire an admin action that calls it and records the reversing amount. Otherwise treat refunds as manual and log the Mollie payment ID so ops can act in the Mollie dashboard. A nightly job that reconciles pending instructions older than N minutes against Mollie is cheap insurance.
Modernizing path. If you inherit this, put your own PaymentGateway interface in front of it. Then you can replace the internals with mollie/mollie-api-php v2 + a hand-written JMS plugin (or drop JMS entirely for new code) without touching controllers.
complete()/re-checks pending instructions.new PaymentInstruction($amount, 'EUR', $name) with a typo or the wrong plugin name produces a "no plugin found" error far from the cause. Copy the string from the plugin's getName().bcmath or a money library and assert currency at both ends of the flow.http:// or the wrong host — set router.request_context parameters or configure trusted proxies, or Mollie will redirect users to the wrong place.payment.plugin tag; read services.xml before overriding anything. The bundle logs almost nothing useful, so log the instruction ID, Mollie payment ID, and Result status yourself — the Mollie dashboard is your other debugging surface.Plugin/*Plugin.php into your app, adapt the API calls, and register it as your own payment.plugin service. Don't monkey-patch the vendor code.PaymentInstruction or Payment, check JMSPaymentCore's entity configuration; this bundle won't tell you.How can I help you explore Laravel packages today?