From qa-payment
Wraps Adyen test-mode patterns: test-API-key initialization, the canonical Adyen test cards (Visa 4111 1111 1111 1111; 5454 5454 5454 5454 3DS frictionless; 4917 6100 0000 0000 3DS 2 challenge), the per-flow result codes (Authorised / Refused / Pending / RedirectShopper / Error), the HMAC-SHA256 webhook validation, and notifications-vs-API duality (Adyen separates synchronous API calls from asynchronous notifications). Use when testing Adyen-integrated code. Composes payment-flow-states-reference + 3ds-test-flow-reference.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-payment:adyen-test-modeThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Adyen's test environment is fully API-compatible with prod. Per
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.
3ds-test-flow-reference.npm install @adyen/api-library
pip install Adyen
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);
Per docs.adyen.com/development-resources/testing/test-card-numbers:
| Card | Behaviour |
|---|---|
| 4111 1111 1111 1111 | Authorised (Visa) |
| 5555 4444 3333 1111 | Authorised (Mastercard) |
| 4000 0000 0000 0119 | Refused (general) |
| 5444 5555 5555 5557 | Refused (insufficient funds) |
| 5454 5454 5454 5454 | 3DS 2 frictionless |
| 4917 6100 0000 0000 | 3DS 2 challenge |
| 4012 8888 8888 1881 | 3DS 1 (deprecated) |
Test expiry: any future date. Test CVC: 737 (special "3DS") or any 3-digit.
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/payment-result-codes:
| resultCode | Meaning |
|---|---|
Authorised | Approved |
Refused | Declined |
Cancelled | Cancelled |
Pending | Async; final result via notification |
RedirectShopper | 3DS / redirect required |
IdentifyShopper | 3DS device-fingerprint step |
ChallengeShopper | 3DS 2 challenge |
Error | Failure |
Adyen sends webhooks ("notifications") for every state change. Per docs.adyen.com/development-resources/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);
});
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);
});
Per Adyen docs: in Customer Area, you can re-send notifications on demand for testing. Also exposes a webhook-events endpoint for replay.
npm test
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-pattern | Why it fails | Fix |
|---|---|---|
| Use prod API key in tests | Real settlement risk | Strict TEST environment |
| Skip HMAC signature validation | Webhook spoofing | Always validate |
Treat resultCode=Pending as failure | Async; final via notification | Wait for webhook |
| One unit test for "happy path" only | Refused / RedirectShopper paths untested | Per-resultCode test |
| Hardcoded merchant account in code | Per-env account; tests pass against wrong account | Env var |
Reply [accepted] even on processing-error | Adyen stops retrying | Reply only on successful processing |
| Skip 3DS 2 challenge test | Required by EU regulations | Test 4917 6100 0000 0000 path |
| Encrypted-data fields wrong format | Adyen rejects with cryptic error | Use Adyen-SDK encryption helpers |
test_* placeholders.payment-flow-states-reference,
3ds-test-flow-reference,
pci-dss-scope-reference.stripe-test-cards-and-webhooks,
paypal-sandbox,
braintree-test-cards.npx claudepluginhub testland/qa --plugin qa-paymentProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.