CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/payment-gateway-sandboxes

Wraps the vendor-generic payment-gateway sandbox pattern - test credentials, sandbox base URLs / environment switches, deterministic test-card matrices, and gateway-native webhook simulators - with per-gateway references for Adyen test mode, PayPal Sandbox, and Braintree sandbox. Use when testing code integrated with Adyen, PayPal, or Braintree; for Stripe use stripe-test-cards-and-webhooks (one-time payments) or stripe-subscription-billing-test-author (recurring billing).

72

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Medium

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

adyen.mdreferences/

Adyen test mode

Adyen's test environment is fully API-compatible with prod. Per docs.adyen.com/development-resources/testing, test API keys + test merchant accounts produce deterministic results based on input.

The notable difference from Stripe: Adyen separates the synchronous payment API from the asynchronous notification webhook more explicitly. Both surfaces need tests.

Install

npm install @adyen/api-library
pip install Adyen

Initialize

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

const client = new Client({
  apiKey: process.env.ADYEN_TEST_API_KEY!,
  environment: 'TEST',                  // vs 'LIVE'
});
const checkout = new CheckoutAPI(client);

Test cards

Per docs.adyen.com/development-resources/test-cards-and-credentials/test-card-numbers:

CardBehaviour
4111 1111 1111 1111Authorised (Visa)
5555 4444 3333 1111Authorised (Mastercard)
4000 0000 0000 0119Refused (general)
5444 5555 5555 5557Refused (insufficient funds)
5454 5454 5454 54543DS 2 frictionless
4917 6100 0000 00003DS 2 challenge
4012 8888 8888 18813DS 1 (deprecated)

Test expiry: any future date. Test CVC: 737 (special "3DS") or any 3-digit.

Payment

const paymentResponse = await checkout.payments({
  amount: { currency: 'EUR', value: 1000 },
  paymentMethod: {
    type: 'scheme',
    encryptedCardNumber: 'test_4111111111111111',
    encryptedExpiryMonth: 'test_03',
    encryptedExpiryYear: 'test_2030',
    encryptedSecurityCode: 'test_737',
  },
  reference: 'order-' + uuidv4(),
  merchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT!,
});

expect(paymentResponse.resultCode).toBe('Authorised');

Result codes per docs.adyen.com/online-payments/build-your-integration/payment-result-codes:

resultCodeMeaning
AuthorisedApproved
RefusedDeclined
CancelledCancelled
PendingAsync; final result via notification
RedirectShopper3DS / redirect required
IdentifyShopper3DS device-fingerprint step
ChallengeShopper3DS 2 challenge
ErrorFailure

Webhook (notification) handling

Adyen sends webhooks ("notifications") for every state change. Per docs.adyen.com/development-resources/webhooks/secure-webhooks/verify-hmac-signatures: validate via HMAC-SHA256 over a canonical-string of the payload.

import { hmacValidator } from '@adyen/api-library';
const validator = new hmacValidator();

test('webhook signature valid', () => {
  const notification = { /* notification payload */ };
  const hmacSignature = notification.additionalData.hmacSignature;
  const isValid = validator.validateHMAC(notification, process.env.ADYEN_HMAC_KEY!);
  expect(isValid).toBe(true);
});

Notification idempotency

Adyen redelivers notifications until acknowledged with HTTP 200

  • [accepted] body. Tests should verify the handler is idempotent under redelivery:
test('redelivered notification handled idempotently', async () => {
  const notif = makeTestNotification();
  await handler(notif);
  const before = await db.payments.count();
  await handler(notif);  // re-delivered
  const after = await db.payments.count();
  expect(after).toBe(before);
});

Notifications test mode

Per Adyen docs: in Customer Area, you can re-send notifications on demand for testing. Also exposes a webhook-events endpoint for replay.

CI integration

jobs:
  adyen-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
      - run: npm ci && npm test
        env:
          ADYEN_TEST_API_KEY: ${{ secrets.ADYEN_TEST_API_KEY }}
          ADYEN_MERCHANT_ACCOUNT: ${{ secrets.ADYEN_TEST_MERCHANT_ACCOUNT }}
          ADYEN_HMAC_KEY: ${{ secrets.ADYEN_TEST_HMAC_KEY }}

Anti-patterns

Anti-patternWhy it failsFix
Use prod API key in testsReal settlement riskStrict TEST environment
Skip HMAC signature validationWebhook spoofingAlways validate
Treat resultCode=Pending as failureAsync; final via notificationWait for webhook
One unit test for "happy path" onlyRefused / RedirectShopper paths untestedPer-resultCode test
Hardcoded merchant account in codePer-env account; tests pass against wrong accountEnv var
Reply [accepted] even on processing-errorAdyen stops retryingReply only on successful processing
Skip 3DS 2 challenge testRequired by EU regulationsTest 4917 6100 0000 0000 path
Encrypted-data fields wrong formatAdyen rejects with cryptic errorUse Adyen-SDK encryption helpers

Limitations

  • Adyen test mode uses encrypted data placeholders. Real encryption uses Adyen Web SDK; tests use test_* placeholders.
  • Notification SLA varies. Real-time webhooks but retried-on-failure can be hours later.
  • Recurring API has separate test cards. Tokenisation scenarios need their own surface.
  • Region-specific payment methods. iDEAL / Sofort / etc. have separate test flows.

Sources

SKILL.md

tile.json