Description
When a customer abandons the Mollie payment page (closes the tab without paying) and later retries payment from their account page, the order gets stuck in an unrecoverable state.
Steps to reproduce
- Customer places an order and selects a Mollie payment method (e.g. iDEAL, PayPal, Klarna)
- Customer is redirected to the Mollie checkout page
- Customer closes the page without paying and goes back to Sylius
- The Sylius Payment remains in state new, but Payment.details still contains the order_mollie_id / payment_mollie_id from the first attempt
- Customer goes to "My Account" → order page → selects a payment method → submits
- A new Payum capture token is generated, CaptureAction is executed
- CaptureAction sees order_mollie_id in details → early returns without creating a new Mollie payment (lines 73-97)
- Payum proceeds to StatusAction → queries the old Mollie payment (status open) → markPending() → Payment transitions to processing
- The thank-you page is displayed, but no real payment happened
- The customer can no longer retry: ChangePaymentMethodType only shows payments in state new or cart — the processing payment is hidden
The order is now permanently stuck.
Root cause
In CaptureAction.php, when payment_mollie_id or order_mollie_id already exists in the payment details, the action returns immediately without checking whether the Mollie payment is still valid or whether the customer is retrying with a
new capture token.
// CaptureAction.php - simplified
if (isset($details['payment_mollie_id']) || isset($details['order_mollie_id'])) {
// checks for QR code / Apple Pay, then:
return; // ← Does nothing. No new Mollie payment is created.
}
This early return is correct for the normal flow (customer returning from Mollie after paying), but it does not distinguish between:
- A return from Mollie (same capture token, same backurl) — should do nothing -> ok
- A retry attempt (new capture token, stale Mollie IDs) — should handle the stale payment -> nok
Expected behavior
When a customer retries payment after abandoning the Mollie checkout page, CaptureAction should detect that the capture token has changed (i.e. this is a retry, not a return from Mollie) and either:
- Check the real Mollie payment status via API
- If the old payment is terminal (expired, canceled, failed): clear the stale IDs and create a new Mollie payment
- If the old payment is still open: redirect to the existing checkout URL (to avoid double charges)
Workaround
We implemented an event listener on sylius.order.pre_update that intercepts the retry form submission before Payum runs. It queries the Mollie API for the real payment status, then:
- If the old Mollie payment is terminal (expired, canceled, failed): fails the Sylius payment (triggering auto-creation of a new clean one) and copies the form-submitted details to it
- If the old Mollie payment is still open with the same method: redirects to the existing Mollie checkout URL
- If the customer changed payment method: fails the old Sylius payment and creates a new one
This works for the common cases, but we believe it does not fully prevent double charges. If the customer has the old Mollie checkout page still open in a browser tab and pays on it after a new Sylius payment has been created, Mollie
will accept both payments. The Sylius state machine naturally blocks the old payment from transitioning (failed → completed is not allowed), so Sylius won't reflect the duplicate — but the money is still collected by Mollie and requires
a manual refund.
We don't think this can be solved at the application level: the Mollie API only supports cancellation for bank transfers and SEPA direct debits. For most methods (PayPal, credit card, Klarna, iDEAL…), there is no way to programmatically
cancel an open payment.
Ideally, CaptureAction should handle the retry scenario natively to minimize the window for this edge case.
Environment
- Sylius: 2.0
- Mollie Plugin: v3.2.2
Description
When a customer abandons the Mollie payment page (closes the tab without paying) and later retries payment from their account page, the order gets stuck in an unrecoverable state.
Steps to reproduce
The order is now permanently stuck.
Root cause
In CaptureAction.php, when payment_mollie_id or order_mollie_id already exists in the payment details, the action returns immediately without checking whether the Mollie payment is still valid or whether the customer is retrying with a
new capture token.
// CaptureAction.php - simplified
if (isset($details['payment_mollie_id']) || isset($details['order_mollie_id'])) {
// checks for QR code / Apple Pay, then:
return; // ← Does nothing. No new Mollie payment is created.
}
This early return is correct for the normal flow (customer returning from Mollie after paying), but it does not distinguish between:
Expected behavior
When a customer retries payment after abandoning the Mollie checkout page, CaptureAction should detect that the capture token has changed (i.e. this is a retry, not a return from Mollie) and either:
Workaround
We implemented an event listener on sylius.order.pre_update that intercepts the retry form submission before Payum runs. It queries the Mollie API for the real payment status, then:
This works for the common cases, but we believe it does not fully prevent double charges. If the customer has the old Mollie checkout page still open in a browser tab and pays on it after a new Sylius payment has been created, Mollie
will accept both payments. The Sylius state machine naturally blocks the old payment from transitioning (failed → completed is not allowed), so Sylius won't reflect the duplicate — but the money is still collected by Mollie and requires
a manual refund.
We don't think this can be solved at the application level: the Mollie API only supports cancellation for bank transfers and SEPA direct debits. For most methods (PayPal, credit card, Klarna, iDEAL…), there is no way to programmatically
cancel an open payment.
Ideally, CaptureAction should handle the retry scenario natively to minimize the window for this edge case.
Environment