Skip to main content
Business & GrowthPayment Services246 lines

Adyen

Accept payments with Adyen. Use this skill when the project needs to integrate

Quick Summary26 lines
You are a payments specialist who integrates Adyen into projects. Adyen is an
enterprise-grade payment platform used by large-scale merchants. It supports
online, in-app, and in-person payments with a single integration.

## Key Points

- Test dashboard: `ca-test.adyen.com`
- Test card: `4111 1111 1111 1111`, expiry any future date, CVC `737`
- 3D Secure test card: `5212 3456 7890 1234`
- Use the `TEST` environment in the API client
- Always verify HMAC signatures on webhooks before processing
- Use `shopperReference` consistently to link Adyen shoppers to your users
- Return `[accepted]` from webhook endpoints — Adyen retries on other responses
- Use Sessions integration (not Advanced flow) for new projects — simpler and PCI-compliant
- Store `pspReference` from every payment for refunds and reconciliation
- Use Adyen's test cards with specific amounts to trigger error scenarios
- Enable 3D Secure for all card payments to shift liability
- Granting access based on the redirect `resultCode` alone without webhook confirmation

## Quick Example

```bash
npm install @adyen/api-library
```
skilldb get payment-services-skills/AdyenFull skill: 246 lines
Paste into your CLAUDE.md or agent config

Adyen Payment Integration

You are a payments specialist who integrates Adyen into projects. Adyen is an enterprise-grade payment platform used by large-scale merchants. It supports online, in-app, and in-person payments with a single integration.

Core Philosophy

Single platform for all channels

Adyen unifies online payments, in-store POS, and mobile into one platform. A single API processes card payments, wallets, bank transfers, and local payment methods across 200+ countries.

Payment methods are market-specific

Adyen lets you enable local payment methods (iDEAL in Netherlands, Bancontact in Belgium, PIX in Brazil) from the dashboard. The Drop-in component renders the right methods automatically based on the shopper's country.

Webhooks are authoritative

Adyen uses asynchronous notifications for payment results. Never rely solely on the API response — always confirm final payment status via webhooks.

Setup

Install

npm install @adyen/api-library

Initialize

import { Client, Config, CheckoutAPI } from '@adyen/api-library';

const config = new Config();
config.apiKey = process.env.ADYEN_API_KEY;
config.merchantAccount = process.env.ADYEN_MERCHANT_ACCOUNT;

const client = new Client({ config });
client.setEnvironment(
  process.env.NODE_ENV === 'production' ? 'LIVE' : 'TEST',
  process.env.ADYEN_LIVE_URL_PREFIX // required for live only
);

const checkout = new CheckoutAPI(client);

Key Techniques

Create a checkout session (Drop-in / Components)

const session = await checkout.PaymentsApi.sessions({
  amount: { currency: 'EUR', value: 2999 }, // amount in minor units
  countryCode: 'NL',
  merchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT,
  reference: `order-${orderId}`,
  returnUrl: `https://yourdomain.com/checkout/result?ref=${orderId}`,
  shopperReference: userId,
  shopperEmail: 'shopper@example.com',
  lineItems: [
    {
      id: 'item-1',
      description: 'Pro Plan',
      quantity: 1,
      amountIncludingTax: 2999,
    },
  ],
});

// Return session.id and session.sessionData to the frontend

Frontend Drop-in component

<script src="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.64.0/adyenWeb.js"></script>
<link rel="stylesheet" href="https://checkoutshopper-test.adyen.com/checkoutshopper/sdk/5.64.0/adyenWeb.css" />

<div id="dropin-container"></div>
<script>
  const { AdyenCheckout, Dropin } = window.AdyenWeb;

  const checkout = await AdyenCheckout({
    environment: 'test',
    clientKey: 'test_xxx',
    session: {
      id: sessionId,       // from server
      sessionData: sessionData, // from server
    },
    onPaymentCompleted: (result) => {
      // result.resultCode: 'Authorised', 'Refused', 'Pending'
      window.location.href = `/checkout/result?resultCode=${result.resultCode}`;
    },
    onError: (error) => console.error(error),
  });

  const dropin = new Dropin(checkout, {
    paymentMethodsConfiguration: {
      card: { hasHolderName: true, holderNameRequired: true },
    },
  });
  dropin.mount('#dropin-container');
</script>

Recurring / subscription payments (tokenization)

// First payment: store the token
const session = await checkout.PaymentsApi.sessions({
  amount: { currency: 'EUR', value: 999 },
  merchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT,
  reference: `sub-initial-${userId}`,
  returnUrl: 'https://yourdomain.com/checkout/result',
  shopperReference: userId,
  shopperInteraction: 'Ecommerce',
  recurringProcessingModel: 'Subscription',
  storePaymentMethod: true,
});

// Subsequent payments: charge the stored token
const result = await checkout.PaymentsApi.payments({
  amount: { currency: 'EUR', value: 999 },
  merchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT,
  reference: `sub-renewal-${Date.now()}`,
  shopperReference: userId,
  shopperInteraction: 'ContAuth',
  recurringProcessingModel: 'Subscription',
  paymentMethod: {
    type: 'scheme',
    storedPaymentMethodId: storedTokenId, // from RECURRING_CONTRACT webhook
  },
});

Refunds and captures

import { ModificationsAPI } from '@adyen/api-library';

const modifications = new ModificationsAPI(client);

// Capture (if using manual capture)
await modifications.captureAuthorisedPayment(pspReference, {
  amount: { currency: 'EUR', value: 2999 },
  merchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT,
  reference: `capture-${orderId}`,
});

// Refund
await modifications.refundCapturedPayment(pspReference, {
  amount: { currency: 'EUR', value: 2999 },
  merchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT,
  reference: `refund-${orderId}`,
});

Webhook Processing

EventAction
AUTHORISATION (success)Grant access / fulfill order
AUTHORISATION (failure)Mark order failed
CAPTUREConfirm funds captured
CANCELLATIONMark order cancelled
REFUNDProcess refund in your system
RECURRING_CONTRACTStore token reference for future charges
CHARGEBACKHandle dispute
import { hmacValidator } from '@adyen/api-library';

export async function POST(req: Request) {
  const body = await req.json();
  const validator = new hmacValidator();

  for (const item of body.notificationItems) {
    const notification = item.NotificationRequestItem;

    // Verify HMAC signature
    if (!validator.validateHMAC(notification, process.env.ADYEN_HMAC_KEY)) {
      return new Response('Invalid HMAC', { status: 401 });
    }

    switch (notification.eventCode) {
      case 'AUTHORISATION':
        if (notification.success === 'true') {
          await fulfillOrder(notification.merchantReference, notification.pspReference);
        } else {
          await markOrderFailed(notification.merchantReference, notification.reason);
        }
        break;

      case 'REFUND':
        if (notification.success === 'true') {
          await processRefund(notification.originalReference);
        }
        break;

      case 'RECURRING_CONTRACT':
        await storePaymentToken(
          notification.additionalData?.['recurring.shopperReference'],
          notification.additionalData?.['recurring.recurringDetailReference']
        );
        break;
    }
  }

  // Adyen requires this exact response
  return new Response('[accepted]', { status: 200 });
}

Testing

Adyen provides a full test environment:

  • Test dashboard: ca-test.adyen.com
  • Test card: 4111 1111 1111 1111, expiry any future date, CVC 737
  • 3D Secure test card: 5212 3456 7890 1234
  • Use the TEST environment in the API client

Best Practices

  • Always verify HMAC signatures on webhooks before processing
  • Use shopperReference consistently to link Adyen shoppers to your users
  • Return [accepted] from webhook endpoints — Adyen retries on other responses
  • Use Sessions integration (not Advanced flow) for new projects — simpler and PCI-compliant
  • Store pspReference from every payment for refunds and reconciliation
  • Use Adyen's test cards with specific amounts to trigger error scenarios
  • Enable 3D Secure for all card payments to shift liability

Anti-Patterns

  • Granting access based on the redirect resultCode alone without webhook confirmation
  • Not returning [accepted] in webhook responses — causes Adyen to retry endlessly
  • Hardcoding payment methods instead of letting Drop-in render them dynamically
  • Skipping HMAC validation on webhooks
  • Using the Advanced flow when Sessions would suffice — more code, more PCI scope
  • Not handling the RECURRING_CONTRACT webhook — you lose the token reference
  • Storing full card details instead of using Adyen tokenization

Install this skill directly: skilldb add payment-services-skills

Get CLI access →