Checkout & payments
Adyen Payment Gateway
Indicative price, not an offer: €24/mo + 0.5% gateway fee; credential: Adyen merchant account setup
Generated from docs/plan/paid/adyen-payment-gateway/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
European and global merchants need local payment methods beyond Stripe and PayPal. This paid credential module adds Adyen integration, supporting iDEAL, Bancontact, Klarna, and other EU/APAC payment methods, with PCI liability transferred to Adyen.
The problem
Customers in the Netherlands want to pay with iDEAL, Germany with Sofortüberweisung, Belgium with Bancontact. Without these local methods, checkout conversion in those regions drops 30–50%, but integrating Adyen is complex and I need support to set up the merchant account.
What it does
- Session creation: POST to Adyen Checkout API, return checkout URL for hosted payment form
- Webhook verification: HMAC signature validation, idempotent event handling (at-least-once delivery)
- Payment state machine: handle Adyen’s transitions (Pending → Authorised → Captured / Failed / Refused)
- Currency/amount validation: reject webhooks where currency or amount don’t match the order
- Refund routing: POST refund requests to Adyen API; track refund status separately from order status
- Multi-method support: iDEAL, Bancontact, Giropay, EPS, Przelewy24, Apple Pay, Google Pay, all within one Adyen merchant account
- Stored payment methods (optional): allow customer to reuse payment instrument on subsequent orders
- PCI compliance: all card data stays on Adyen’s servers; AstroBaaS only handles session ids and webhook events
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Chargeback management — Adyen manages disputes; AstroBaaS logs the webhook but does not alter order status automatically on chargeback. Merchant monitors Adyen dashboard.
- Risk scoring or 3D Secure enforcement — Adyen applies its own fraud rules and SCA challenges. Module trusts Adyen’s decision and passes the webhook result through.
- Settlement to merchant bank account — that happens in Adyen’s system. AstroBaaS never touches settlement; the module only tracks payment status.
- Ledger reconciliation — module records what Adyen said; reconciling Adyen reports to bank statements is merchant’s accounting work.
Data model
No new collections. Store on Order: payment_reference (Adyen’s PSP Reference, string), payment_provider (‘adyen’), payment_method (string, e.g., ‘ideal’, ‘bcmc’, ‘applepay’). payment_status lifecycle: ‘unpaid’ → ‘pending’ (webhook arrives with AUTHORIZE) → ‘paid’ (webhook arrives with CAPTURE) or ‘failed’ (REFUSED). refunds array unchanged.
API
- POST /api/payments/start — body: { order_number, email, provider: ‘adyen’, payment_method: ‘ideal’ | ‘bcmc’ | … } → { redirect_url }
- POST /api/payments/webhook/adyen — Adyen calls this; verify HMAC, idempotent state update
- GET /api/orders/{id}/payment — show payment_status, payment_provider, payment_method, refunds
- POST /api/orders/{id}/refund — { amount_cents } → forward to Adyen API, track refund status
- GET /api/payments/methods/adyen — list available payment methods for this shop (merchant configured in env vars)
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Payment Providers dashboard shows Adyen status: ‘Configured’ if credentials present, ‘Misconfigured’ if missing. Merchant enters ADYEN_API_KEY, ADYEN_MERCHANT_ACCOUNT, ADYEN_WEBHOOK_SECRET in Settings or via .env. Test-mode toggle allows sandbox testing before going live. Order detail screen shows payment method (e.g., ‘iDEAL’) and refund history.
The seam — why this is paid
Core owns Order, webhook routing, and refund ledger. Paid module owns Adyen session creation, HMAC verification, and method routing. The seam: core’s POST /api/payments/start is generic; Adyen is one implementation. A merchant who does not buy this module has Stripe or PayPal instead. Adyen’s PCI compliance means AstroBaaS never sees card numbers; the credential (API key) is environment-only, never stored in settings DB.
Credential: Adyen API key management, local payment method support, PCI liability
Dependencies
- payment-status-tracking — module relies on payment_status enum and refund tracking
- production-payment-gateway-certification — Adyen’s live credentials must be tested in sandbox before production use
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- Adyen API key configured; merchant selects ‘iDEAL’ at checkout, is redirected to Adyen’s hosted form
- Webhook arrives from Adyen with HMAC signature; signature fails (wrong secret) → 401, order remains ‘unpaid’
- Webhook arrives with correct signature, amount €50.00, currency EUR, order amount €50.00 EUR → order becomes ‘paid’ + ‘processing’
- Same webhook replayed (same event id) → no-op, order stays ‘paid’
- Webhook arrives for wrong amount (€60.00) or wrong currency (GBP) → order rejected as suspicious, audit-logged
- Merchant refunds €50.00 via API → request forwarded to Adyen, refund_status=‘pending’; Adyen webhook later confirms REFUND_SUCCESS
- Merchant switches payment method mid-checkout from iDEAL to Bancontact → new session created with new method
- Apple Pay not available in merchant’s Adyen account → method doesn’t appear in /api/payments/methods/adyen response
Risks
Adyen API credential in env vars can be leaked if .env is accidentally committed — CI/CD must block .env commits and rotate credentials if leaked. HMAC verification must use constant-time comparison (built-in to all crypto libs, but easy to forget). If session creation succeeds but webhook never arrives, order stays ‘pending’ forever — merchant needs manual retry logic (e.g., ‘complete payment’ button that re-initiates the session with the same order). Refund race: if admin clicks refund twice, both requests go to Adyen; idempotency depends on Adyen’s API (Adyen uses idempotency keys, so this is safe, but it must be documented).
Commercial context
| Suggested price | €24/mo + 0.5% gateway fee; credential: Adyen merchant account setup |
| Rival anchor | Adyen: €0 + 1.5-2.5% commission; Stripe: €0 + 1.4-2.9%; Shopify Payments: 2.2-2.9% |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.