E-Commerce — Payments¶
OWNER: aimee (scoping) ALSO_USED_BY: anna (spec), urszula, maxim (backend), floris, floor (frontend) LAST_VERIFIED: 2026-03-26
Overview¶
Payments are the most legally sensitive part of any e-commerce project. GE uses Mollie as the primary payment provider for EU/NL projects — it is headquartered in Amsterdam, regulated by De Nederlandsche Bank, PCI DSS Level 1, and handles PSD2/SCA compliance automatically. Stripe is the secondary option for international-heavy projects (US-based — EU data sovereignty risk).
Never store raw card data. Never build custom payment forms. Always use the provider's hosted checkout or embeddable components.
Feature Decomposition¶
Payment Provider Integration¶
SCOPE_ITEM: Mollie integration (primary, EU/NL) INCLUDES: Mollie API client, payment creation, webhook handler, payment status polling (fallback), hosted checkout redirect flow OPTIONAL: Mollie Components (embedded card form), Mollie Checkout customization COMPLIANCE: PCI DSS Level 1 via Mollie. No raw card data touches our servers. ESTIMATE_COMPLEXITY: normal
SCOPE_ITEM: Stripe integration (secondary, international only) INCLUDES: Stripe API client, PaymentIntent creation, webhook handler, Stripe Checkout or Elements OPTIONAL: Stripe Connect (for marketplace split payments), Stripe Tax COMPLIANCE: PCI DSS Level 1 via Stripe. SCA handled via Stripe.js 3D Secure flow. NOTE: US-based service. Use only if client explicitly requires international coverage beyond EU. EU data sovereignty risk. ESTIMATE_COMPLEXITY: normal
CHECK: Which provider? IF: NL/EU focus, iDEAL required → Mollie (iDEAL is 70%+ of NL online payments) IF: international focus, US customers → Stripe IF: marketplace with split payments → Mollie Connect or Stripe Connect IF: client already has provider account → use their existing provider
Payment Methods¶
SCOPE_ITEM: iDEAL INCLUDES: Bank selection, redirect to bank, return handling, instant confirmation COMPLIANCE: PSD2 — SCA inherent (bank login = strong authentication) ESTIMATE_COMPLEXITY: simple (via Mollie)
SCOPE_ITEM: Credit/debit cards (Visa, Mastercard, Amex) INCLUDES: Card input via hosted checkout or Mollie Components, 3D Secure 2.0 (automatic), card brand detection COMPLIANCE: PSD2/SCA — 3D Secure required for EU card payments. Mollie handles exemption logic (low-risk, low-value, recurring). ESTIMATE_COMPLEXITY: simple (via Mollie)
SCOPE_ITEM: Bancontact INCLUDES: Redirect to Bancontact, return handling COMPLIANCE: Belgium's primary payment method. Include if client sells to Belgium. ESTIMATE_COMPLEXITY: simple (via Mollie)
SCOPE_ITEM: SEPA Direct Debit INCLUDES: Mandate creation, first payment, recurring collection OPTIONAL: One-off SEPA transfers COMPLIANCE: Mandate must be explicitly accepted by consumer. 8-week refund right for authorized debits, 13 months for unauthorized. ESTIMATE_COMPLEXITY: normal
SCOPE_ITEM: SEPA Bank Transfer INCLUDES: Transfer details shown to customer, pending status until received, auto-matching via Mollie OPTIONAL: Expiry date on transfer (default 12 days via Mollie) COMPLIANCE: Slow settlement (1-3 business days). Inform customer of delay. ESTIMATE_COMPLEXITY: simple
SCOPE_ITEM: PayPal INCLUDES: PayPal redirect flow, return handling OPTIONAL: PayPal Express (skip checkout, pay directly from cart) ESTIMATE_COMPLEXITY: simple (via Mollie)
SCOPE_ITEM: Klarna (buy now, pay later) INCLUDES: Klarna Pay Later (invoice, 14-30 days), Klarna Slice It (installments) OPTIONAL: Klarna Pay Now (immediate) COMPLIANCE: Consumer credit regulations apply. Klarna handles credit check. Age restriction (18+). ESTIMATE_COMPLEXITY: simple (via Mollie)
SCOPE_ITEM: Apple Pay / Google Pay INCLUDES: Wallet detection, payment sheet, tokenized payment COMPLIANCE: SCA satisfied by device biometric. Domain verification required for Apple Pay. ESTIMATE_COMPLEXITY: simple (via Mollie)
CHECK: Which payment methods to offer? IF: NL-only → iDEAL (mandatory) + cards + Bancontact + PayPal IF: NL + Belgium → add Bancontact IF: EU-wide → add SEPA, Klarna, Apple Pay, Google Pay IF: young demographic → add Klarna (BNPL popular with 18-35)
Webhook Handling¶
SCOPE_ITEM: Payment webhook processing INCLUDES: Webhook endpoint (POST /api/webhooks/mollie), signature verification, idempotent processing (process same webhook multiple times safely), order status update based on payment status COMPLIANCE: Webhook endpoint must be publicly accessible. Must respond 200 within 15 seconds. Must handle retries. ESTIMATE_COMPLEXITY: normal
Payment status mapping:
paid → order.status = confirmed, send confirmation email, decrease stock
failed → order.status = payment_failed, release stock reservation
cancelled → order.status = cancelled, release stock reservation
expired → order.status = cancelled, release stock reservation
pending → order.status = pending_payment (no action, wait for next webhook)
refunded → order.status = refunded, trigger refund flow
charged_back → order.status = disputed, alert admin
Implementation Pattern¶
POST /api/payments/create
1. Validate cart (stock, prices)
2. Create order in DB (status: pending_payment)
3. Create Mollie payment:
- amount: order total (VAT inclusive)
- description: "Order #GE-{orderId}"
- redirectUrl: /checkout/return?orderId={orderId}
- webhookUrl: /api/webhooks/mollie
- metadata: { orderId }
- method: selected payment method (or omit for Mollie checkout)
4. Store payment.id on order
5. Return payment._links.checkout.href (redirect URL)
POST /api/webhooks/mollie
1. Receive { id: "tr_xxx" }
2. Fetch payment from Mollie API (NEVER trust webhook body for status)
3. Find order by payment ID
4. IF order already in terminal state → return 200 (idempotent)
5. Update order status based on payment status
6. Trigger side effects (email, stock, etc.)
7. Return 200
CHECK: Never trust webhook body for payment status IF: webhook received → ALWAYS fetch payment from Mollie API to get current status THEN: update order based on fetched status, not webhook payload
Refunds¶
SCOPE_ITEM: Full refund INCLUDES: Admin triggers refund, Mollie API refund call, refund status tracking, customer notification email OPTIONAL: Reason code (returned, damaged, duplicate, customer request) COMPLIANCE: EU 14-day withdrawal — refund must be issued within 14 days of receiving returned goods (or withdrawal notification for services) ESTIMATE_COMPLEXITY: simple
SCOPE_ITEM: Partial refund INCLUDES: Admin specifies amount, Mollie API partial refund, refund line items tracking OPTIONAL: Item-level refund (refund specific products, keep others) ESTIMATE_COMPLEXITY: normal
SCOPE_ITEM: Refund policy enforcement INCLUDES: Configurable refund window (default 14 days per EU law), refund to original payment method COMPLIANCE: EU consumer rights — refund to original payment method unless consumer explicitly agrees otherwise ESTIMATE_COMPLEXITY: simple
Subscriptions & Recurring Payments¶
SCOPE_ITEM: Recurring payments via Mollie Mandates INCLUDES: First payment creates mandate (via iDEAL, card, or SEPA DD), subsequent charges via mandate, subscription management (create, pause, cancel, update amount) COMPLIANCE: SEPA Direct Debit mandate rules. Customer must be able to cancel anytime. PSD2 — mandate is not SCA-exempt for first payment. ESTIMATE_COMPLEXITY: complex
SCOPE_ITEM: Subscription billing INCLUDES: Billing cycle management (weekly, monthly, quarterly, annual), proration on plan changes, failed payment retry (3 attempts over 7 days), dunning emails (payment failed, last warning, subscription cancelled) OPTIONAL: Trial periods, usage-based billing ESTIMATE_COMPLEXITY: complex
Invoice Generation¶
SCOPE_ITEM: Order invoices INCLUDES: Auto-generated PDF invoice on order confirmation, sequential invoice numbers (legally required), seller details, buyer details, line items with VAT, VAT total, payment method COMPLIANCE: EU invoice requirements — invoice number, date, seller VAT number, buyer details, net amount, VAT rate, VAT amount, gross amount. NL: must store for 7 years. ESTIMATE_COMPLEXITY: normal
SCOPE_ITEM: Credit notes INCLUDES: Auto-generated on refund, references original invoice number, negative amounts COMPLIANCE: Required by EU tax law for refunds ESTIMATE_COMPLEXITY: simple
Database Schema Pattern¶
payments (id, order_id, provider, provider_payment_id, method, status, amount_cents, currency, created_at, updated_at)
refunds (id, payment_id, provider_refund_id, amount_cents, reason, status, created_at)
mandates (id, user_id, provider, provider_mandate_id, method, status, created_at)
subscriptions (id, user_id, mandate_id, plan_id, status, current_period_start, current_period_end, cancelled_at, created_at)
invoices (id, order_id, invoice_number, type, pdf_url, created_at)
PSD2 / SCA Reference¶
What: Strong Customer Authentication requires two of three factors: knowledge (password/PIN), possession (phone/card), inherence (fingerprint/face).
When required: All electronic payments in the EEA, with exemptions.
Exemptions Mollie handles automatically: - Low-value transactions (under EUR 30, max 5 consecutive or EUR 100 cumulative) - Low-risk transactions (Transaction Risk Analysis by Mollie) - Recurring payments (after first SCA-authenticated payment) - Merchant-initiated transactions
PSD3 transition: Expected late 2027/early 2028. Mollie will handle compliance. No merchant action needed until then.
Mollie Pricing Reference (2026)¶
| Method | Transaction Fee |
|---|---|
| iDEAL | EUR 0.29 |
| Credit cards (EU) | 1.8% + EUR 0.29 |
| Credit cards (non-EU) | 2.8% + EUR 0.29 |
| Bancontact | EUR 0.39 |
| SEPA Direct Debit | EUR 0.25 |
| PayPal | EUR 0.35 + variable |
| Klarna Pay Later | EUR 0.29 + variable |
| Apple Pay / Google Pay | Card rates apply |
No monthly fees. No setup fees. No minimum commitment.
Anti-Patterns¶
ANTI_PATTERN: Building a custom card input form FIX: Use Mollie Components or hosted checkout. Custom forms require PCI SAQ-A-EP or higher.
ANTI_PATTERN: Trusting webhook body for payment status FIX: Always fetch payment from provider API. Webhooks can be spoofed or replayed.
ANTI_PATTERN: No webhook signature verification FIX: Verify Mollie webhook by fetching payment (Mollie uses fetch-based verification, not signatures). For Stripe, verify webhook signature.
ANTI_PATTERN: Synchronous payment processing FIX: Payment is always async. Create order → redirect → webhook confirms. Never assume payment succeeded from redirect.
ANTI_PATTERN: No idempotent webhook handling FIX: Check if order already processed before updating. Mollie sends multiple webhooks for same payment.
ANTI_PATTERN: Refund to different payment method without consent FIX: EU law — refund to original payment method by default. Different method only with explicit consumer consent.
Cross-References¶
READ_ALSO: wiki/docs/archetypes/e-commerce/cart-checkout.md READ_ALSO: wiki/docs/archetypes/e-commerce/order-management.md READ_ALSO: wiki/docs/archetypes/e-commerce/compliance.md READ_ALSO: wiki/docs/archetypes/e-commerce/integrations.md READ_ALSO: wiki/docs/stack/hono/index.md