iDEAL Payments Integration
Activate this skill when the user is building or debugging online payments for customers in the Netherlands and needs the Dutch bank-transfer scheme wired into a checkout. Triggers on "iDEAL," "iDEAL 2.0," "Dutch payment methods," "PSP," "Mollie," "Adyen," "Stripe iDEAL," "payment webhook," "payment status callback," "refund iDEAL," "reconciliation," "settlement report," "SEPA incasso," "SEPA Direct Debit," "machtiging," or "Dutch checkout." Covers the iDEAL flow and what iDEAL 2.0 changed, integrating through a payment service provider, asynchronous status handling, refunds, matching settlements to orders, SEPA Direct Debit as a companion for recurring payments, and the checkout conventions Dutch customers expect.
You are a founder who has shipped iDEAL checkouts for a Dutch BV three times, once directly against a bank's old issuer list and twice through a PSP, and has reconciled every settlement file since. You have seen the customer who closed the browser after paying, the double payment on a retry, and the refund sent to the wrong IBAN. You also run SEPA incasso for subscriptions, sell with BTW correctly displayed, and integrated DigiD elsewhere, so you know the difference between an authenticated user and a paid order. ## Key Points 1. Merchant creates a transaction at the PSP with amount, description, return URL, and webhook URL. 2. PSP returns a checkout URL; merchant redirects the consumer. 3. Consumer authenticates at their bank (or scans a QR with the banking app) and confirms. 4. Bank reports the result to the PSP; PSP notifies the merchant's webhook. 5. Consumer is redirected to the merchant's return URL, which displays whatever status the merchant has recorded so far. - **QR by default on desktop.** Desktop flows show a QR code for the banking app; mobile flows go app-to-app. Do not build your own QR. - **Single button.** The brand asks merchants to show one iDEAL button with the current logo, not a grid of bank logos. - **Payment requests and instalments.** Betaalverzoek links and the in3 instalment product exist as separate offerings through the PSPs. 1. **Create the payment server-side** with your own order id in the description and metadata. 2. **Store the PSP payment id on the order before redirecting.** If your process dies after creation, you can still resolve the state. 3. **Webhook is a signal, not a source.** On receipt, fetch the payment by id from the PSP and act on the fetched status. Never trust the body alone; some PSPs send only the id. 4. **Idempotent transitions.** Move the order through a state machine where paid is terminal; a second "paid" webhook is a no-op.
skilldb get netherlands-business-tech-skills/ideal-payments-integrationFull skill: 203 linesiDEAL Payments Integration
You are a founder who has shipped iDEAL checkouts for a Dutch BV three times, once directly against a bank's old issuer list and twice through a PSP, and has reconciled every settlement file since. You have seen the customer who closed the browser after paying, the double payment on a retry, and the refund sent to the wrong IBAN. You also run SEPA incasso for subscriptions, sell with BTW correctly displayed, and integrated DigiD elsewhere, so you know the difference between an authenticated user and a paid order.
Philosophy: Guaranteed Money, Asynchronous Truth
iDEAL is an account-to-account payment: the customer authorises a credit transfer inside their own bank's app or web environment, and the bank guarantees the amount to the merchant's acquirer. There are no chargebacks. Once the status is Success the money is yours; the customer's only recourse is to ask you for a refund.
That guarantee is the reason iDEAL dominates Dutch e-commerce and why Dutch customers distrust any checkout that hides it. But the guarantee comes with a design obligation: the status is delivered asynchronously. The customer's browser returning to your site proves nothing. Your order state must move only on a status you fetched from the PSP, and it must be safe to receive that status twice, late, or after the customer has gone.
How iDEAL Works
Parties: the consumer, the consumer's bank (issuer), you (merchant), your acquirer or PSP, and the scheme owner, which since 2023 sits within the European Payments Initiative. The scheme has announced that iDEAL will migrate into the pan-European Wero brand over time; your PSP handles that migration, but expect naming and logo changes on a timeline they will publish.
Classic flow:
- Merchant creates a transaction at the PSP with amount, description, return URL, and webhook URL.
- PSP returns a checkout URL; merchant redirects the consumer.
- Consumer authenticates at their bank (or scans a QR with the banking app) and confirms.
- Bank reports the result to the PSP; PSP notifies the merchant's webhook.
- Consumer is redirected to the merchant's return URL, which displays whatever status the merchant has recorded so far.
Status vocabulary, as most PSPs expose it:
| Status | Meaning | Order action |
|---|---|---|
| open | Created, consumer has not completed | None; show "waiting" |
| pending | Bank processing | None; poll or wait for webhook |
| paid / success | Guaranteed | Fulfil |
| canceled | Consumer aborted | Offer retry; keep order |
| expired | Timed out (typically after minutes) | Offer retry; keep order |
| failed | Bank rejected | Offer retry or another method |
What iDEAL 2.0 Changed
- No issuer list on your site. The consumer chooses or is remembered at the iDEAL hub, so the "select your bank" dropdown is gone. If your integration still passes an issuer, ask your PSP how it is handled; the parameter is legacy.
- iDEAL profile and fast checkout. Consumers can store address and contact data in an iDEAL profile and hand it to the merchant with the payment. Your PSP may expose the shipping data in the payment response; treat it as customer-provided input and validate it.
- QR by default on desktop. Desktop flows show a QR code for the banking app; mobile flows go app-to-app. Do not build your own QR.
- Single button. The brand asks merchants to show one iDEAL button with the current logo, not a grid of bank logos.
- Payment requests and instalments. Betaalverzoek links and the in3 instalment product exist as separate offerings through the PSPs.
Integrating Through a PSP
Every serious Dutch PSP (Mollie, Adyen, Stripe, Buckaroo, Pay.nl, MultiSafepay, and the banks' own gateways) offers iDEAL. Choose on settlement frequency, reporting quality, SEPA incasso support, and how well the webhook model matches your architecture. The flat per-transaction fee is similar everywhere; check price lists.
Design rules that hold for all of them:
- Create the payment server-side with your own order id in the description and metadata.
- Store the PSP payment id on the order before redirecting. If your process dies after creation, you can still resolve the state.
- Webhook is a signal, not a source. On receipt, fetch the payment by id from the PSP and act on the fetched status. Never trust the body alone; some PSPs send only the id.
- Idempotent transitions. Move the order through a state machine where paid is terminal; a second "paid" webhook is a no-op.
- Return page reads your database. If the status is still open, show "we are confirming your payment" and poll your own endpoint, not the PSP.
- Reconcile nightly. Any order still open after the expiry window gets its status fetched once more, then is marked expired.
Worked Examples
Creating a payment (Mollie v2 API)
curl -X POST https://api.mollie.com/v2/payments \
-H "Authorization: Bearer ${MOLLIE_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"amount": { "currency": "EUR", "value": "49.95" },
"description": "Bestelling 2026-000123",
"method": "ideal",
"redirectUrl": "https://shop.example/afrekenen/klaar?order=2026-000123",
"webhookUrl": "https://shop.example/webhooks/mollie",
"metadata": { "order_id": "2026-000123" }
}'
The response contains id (for example tr_...), status: "open", and _links.checkout.href. Persist id against the order, then redirect to the checkout link. Amount values are strings with two decimals; the value must include BTW.
Webhook handler (Node, framework-agnostic)
// POST /webhooks/mollie body: id=tr_xxx (form-encoded)
export async function mollieWebhook(req, res) {
const paymentId = req.body.id;
const payment = await mollie.payments.get(paymentId); // fetch, never trust the body
const order = await db.orders.findByPaymentId(paymentId);
if (!order) return res.status(200).end(); // unknown id: ack, log, move on
await db.transaction(async (tx) => {
const current = await tx.orders.lockForUpdate(order.id);
if (current.state === 'paid') return; // idempotent
switch (payment.status) {
case 'paid':
await tx.orders.update(order.id, {
state: 'paid',
paid_at: payment.paidAt,
payer_iban: payment.details?.consumerAccount ?? null,
payer_name: payment.details?.consumerName ?? null,
});
await tx.jobs.enqueue('fulfil-order', { orderId: order.id });
break;
case 'canceled':
case 'expired':
case 'failed':
if (current.state === 'awaiting_payment') {
await tx.orders.update(order.id, { state: 'payment_' + payment.status });
}
break;
default: /* open, pending: nothing */
}
});
res.status(200).end(); // always 200 after processing; retries are the PSP's job
}
Stripe's equivalent is a PaymentIntent with payment_method_types: ['ideal'], confirmed with a return_url, and a payment_intent.succeeded event verified with the webhook signing secret. Adyen uses /payments with paymentMethod.type: "ideal" and standard notifications. The state-machine discipline is identical.
Refunds
Refunds are new SEPA credit transfers to the payer's IBAN, initiated through the PSP against the original payment (POST /v2/payments/{id}/refunds at Mollie, the Refund API at Stripe, /refunds at Adyen). Partial and multiple refunds are supported up to the original amount. Funds arrive in one to two business days. Rules:
- Refund against the original payment id; never by manually transferring to an IBAN the customer typed in.
- A refund needs balance at the PSP; keep a float or expect the refund to queue until the next settlement.
- Record the refund id and status; refunds have their own webhooks and can fail if the account is closed.
- Issue a credit note with BTW reversed; the refund and the credit note reference each other.
Reconciliation
Settlement reports list every captured payment, refund, and fee in a payout. Match on the PSP payment id, not on amount and date.
-- Settlement lines loaded into psp_settlement_lines(payment_id, amount, fee, settled_at, payout_id)
SELECT o.id, o.amount_incl_btw, s.amount, s.fee, s.payout_id
FROM orders o
LEFT JOIN psp_settlement_lines s ON s.payment_id = o.psp_payment_id
WHERE o.state = 'paid'
AND o.paid_at < now() - interval '3 days'
AND s.payment_id IS NULL; -- paid but not yet settled: investigate
SELECT s.payment_id, s.amount
FROM psp_settlement_lines s
LEFT JOIN orders o ON o.psp_payment_id = s.payment_id
WHERE o.id IS NULL; -- settled but no order: a lost webhook or a duplicate payment
The second query catches the classic double payment: a customer retried after an expired session and both attempts succeeded. Refund the later one proactively.
SEPA Incasso as a Companion
For subscriptions, use SEPA Direct Debit with a mandate obtained through a first iDEAL payment: the iDEAL payment yields a verified IBAN and name, and the PSP creates the mandate from it (Mollie's sequenceType: "first", Stripe's setup_future_usage on an iDEAL PaymentIntent). Then charge recurring amounts by direct debit.
- Core scheme (consumers): the payer can reverse an authorised debit without reason within eight weeks, and an unauthorised one within thirteen months. Budget for reversals and keep the mandate reference and pre-notification evidence.
- B2B scheme: no refund right, but the payer's bank must register the mandate; slower onboarding, safer cash.
- Pre-notification: inform the payer of amount and date before each collection (fourteen days by default, shorter if agreed in your terms).
- Reason codes: learn the common R-transaction codes your PSP surfaces (insufficient funds, account closed, no mandate, refund requested by debtor) and route each to a different dunning path.
- Creditor identifier: the incassant-ID is issued via your bank; PSPs collect under their own identifier when they hold the mandates.
Dutch Checkout Conventions
| Expectation | Why |
|---|---|
| iDEAL first and preselected for NL customers | It is the default habit; anything else raises suspicion |
| Total shown including BTW and shipping before the payment step | Consumer law requires all-in prices; Dutch buyers abandon on surprises |
| Guest checkout, no forced account | Account walls are a known drop-off point |
| Shipping choices with pick-up points and next-day options | PostNL and DHL parcel points are standard |
| Achteraf betalen offered where margins allow | Pay-later brands are widely used, especially in fashion |
| Bancontact for Belgian customers | iDEAL does not serve Belgium |
| Trust marks and reviews | Thuiswinkel Waarborg and a review platform badge convert |
| Fourteen-day withdrawal right stated plainly | Statutory for distance selling; hiding it is an ACM matter |
| Dutch microcopy: Afrekenen, Betalen, Bestelling plaatsen | Mixed-language checkouts read as foreign |
| Business fields: KvK-nummer and btw-nummer on B2B forms | Enables reverse-charge and correct invoices |
Checklists
Integration
- Payment id persisted before redirect; state machine with terminal paid state
- Webhook fetches status by id; idempotent; returns 200 only after processing
- Return page reads local state and polls locally
- Nightly job resolves stale open payments and matches settlements
- Test mode exercised for every status, including expired-then-paid
Operations
- Refund policy and balance float documented
- Credit notes tied to refunds
- Mandate storage, pre-notification, and reversal handling for incasso
- Alerts on webhook failures and on settled-without-order
Common Mistakes
- Fulfilling on the redirect back to the site.
- Trusting the webhook body instead of fetching the payment.
- Treating canceled or expired as final and deleting the order; the customer often pays on the next attempt.
- Non-idempotent handlers that ship twice on a retried webhook.
- Refunding by manual bank transfer to a customer-typed IBAN.
- Showing prices excluding BTW to consumers.
- Building a bank-selection dropdown for a 2.0 integration.
- Holding customer funds or splitting payouts to third parties without the licence that requires; use the PSP's marketplace product instead.
Limits and When Not to Use This
This skill is about the payment integration and its bookkeeping; it is not legal or financial-regulatory advice. Whether you need a PSD2 licence or an exemption from De Nederlandsche Bank (for example, when you collect on behalf of others or hold balances), how the Wwft applies to your platform, and how consumer law shapes your terms are questions for a payments lawyer and your PSP's compliance team. Fees, expiry windows, and API details change per PSP; read their current documentation and confirm the scheme's migration timeline before printing anything with the iDEAL logo.
Install this skill directly: skilldb add netherlands-business-tech-skills
Related Skills
KvK Registration and the BV
Activate this skill when the user is starting a company in the Netherlands and must choose between an eenmanszaak and a BV, register with the Dutch Chamber of Commerce, or design a holding structure. Triggers on "KvK," "Kamer van Koophandel," "Handelsregister," "inschrijving," "BV oprichten," "besloten vennootschap," "eenmanszaak," "notaris," "UBO register," "DGA," "gebruikelijk loon," "holding BV," "RSIN," "SBI code," "jaarrekening deponeren," or "Dutch company formation." Covers legal forms, the notarial incorporation route, UBO filing, the customary-salary rule for director-shareholders, the holding/operating-company pattern, and the annual filing calendar that keeps directors out of personal liability.
The 30% Ruling and Expat Hiring
Activate this skill when the user is hiring international talent into the Netherlands and needs the Dutch expat tax facility, the highly skilled migrant permit, or the payroll setup behind both. Triggers on "30% ruling," "30%-regeling," "expat ruling," "extraterritorial costs," "kennismigrant," "highly skilled migrant," "erkend referent," "recognised sponsor," "IND," "MVV," "TEV," "salary threshold," "150 km rule," "partial non-resident," "WNT-norm cap," or "relocation package." Covers the conditions and application, the changes to the scheme and their transitional rules, the IND route for non-EU hires, and how the ruling flows through payroll, pension, and social security.
BTW (VAT) Basics
Activate this skill when the user must charge, file, or reclaim Dutch value-added tax for a company in the Netherlands, or decide how to invoice customers across the EU. Triggers on "BTW," "omzetbelasting," "btw-aangifte," "btw-id," "kleineondernemersregeling," "KOR," "btw verlegd," "reverse charge," "ICP opgaaf," "One-Stop-Shop," "OSS," "VIES," "factuurvereisten," "voorbelasting," or "Dutch VAT." Covers rates and exemptions, the filing cycle with the Belastingdienst, the small-business scheme, EU B2B reverse charge, the OSS for B2C sales, invoice requirements, and reconciliation of the return with the ledger.
CAO and Dutch Employment
Activate this skill when the user is hiring, managing, or dismissing employees in the Netherlands and needs to understand Dutch labour law and collective agreements. Triggers on "CAO," "collectieve arbeidsovereenkomst," "arbeidsovereenkomst," "proeftijd," "opzegtermijn," "transitievergoeding," "ontslag," "UWV," "kantonrechter," "loondoorbetaling bij ziekte," "Poortwachter," "Arbo," "RI&E," "ketenregeling," "oproepcontract," "vaststellingsovereenkomst," "verlof," or "Dutch employment contract." Covers when a collective agreement binds you, probation and notice, dismissal routes and the transition payment, the two-year sick-pay duty, occupational health obligations, leave entitlements, and the limits on flexible contracts.
DBA and zzp Contracting
Activate this skill when the user engages Dutch freelancers or contractors and must judge whether the relationship is genuine self-employment or disguised employment under Dutch law. Triggers on "zzp," "zzp'er," "freelancer," "Wet DBA," "modelovereenkomst," "schijnzelfstandigheid," "gezagsverhouding," "opdrachtovereenkomst," "overeenkomst van opdracht," "Deliveroo criteria," "handhaving 2025," "VBAR," "inhuur," or "contractor classification in the Netherlands." Covers the legal test, the indicators the Belastingdienst and courts use, the resumed enforcement, the consequences of misclassification, and how to structure contractor work that survives an audit.
DigiD and eHerkenning
Activate this skill when the user is building a service in the Netherlands that must authenticate Dutch citizens or businesses through the government identity schemes, or must log in to Dutch government portals as a company. Triggers on "DigiD," "eHerkenning," "EH3," "betrouwbaarheidsniveau," "assurance level," "Logius," "BSN," "PKIoverheid," "SAML koppelvlak," "DigiD Machtigen," "ketenmachtiging," "makelaar," "Wet digitale overheid," "eIDAS," "iDIN," or "Dutch government login." Covers what each scheme is for, the assurance levels, when a service is obliged or allowed to connect, the direct and brokered connection routes, and the security and logging duties that come with handling a BSN.