From qa-payment
Pure-reference catalog of 3-D Secure (3DS) authentication test flows. Covers the EMVCo 3DS 2.x spec (3DS 1.0 is deprecated since 2022), the frictionless / challenge / not-applicable flow paths, the Strong Customer Authentication (SCA) requirement under EU PSD2 (mandatory since 2021 for most EU card transactions), the test-card patterns each gateway provides (Stripe / Adyen / Braintree authentication challenges via specific PANs), and the testable behaviours per flow. Use when designing 3DS test coverage or auditing existing 3DS flow implementation. Composes payment-flow-states-reference.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-payment:3ds-test-flow-referenceThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
3-D Secure (3DS) is the EMVCo-specified authentication protocol
3-D Secure (3DS) is the EMVCo-specified authentication protocol for online card payments. Per emvco.com, 3DS 2.x is the current spec (cite by stable ID: EMV 3-D Secure Protocol Specification v2.x); 3DS 1.0 is deprecated.
3DS adds a verification step between the merchant + the issuing bank. The customer may be challenged with a one-time code, biometric prompt, or other factor - or pass through frictionless based on risk signals.
Per EMVCo 3DS 2.x spec:
| Outcome | Customer experience | Liability |
|---|---|---|
| Frictionless | No challenge; issuer authenticates based on risk signals | Shifts to issuer (in many regions) |
| Challenge | Customer prompted (SMS, biometric, app) | Shifts to issuer on success |
| Not applicable | 3DS not invoked (non-EU; merchant-initiated) | Stays with merchant |
The merchant's job: send all available data to the gateway; the gateway + issuer + 3DS server decide the flow.
Per European Banking Authority RTS on SCA: since September 2019 (enforcement gradual through 2021), EU card payments require two of:
Exemptions: low-value (< €30), recurring, trusted-beneficiary, merchant-initiated. Each exemption has tracking requirements.
Per stripe.com/docs/testing#regulatory-cards:
| Card | Behaviour |
|---|---|
| 4000 0027 6000 3184 | Authentication required (challenge) |
| 4000 0025 0000 3155 | Authentication required (challenge), payment failure after success |
| 4000 0000 0000 3220 | Authentication required (challenge), payment success |
| 4000 0000 0000 3055 | 3DS supported but not required (frictionless) |
| 4242 4242 4242 4242 | Standard test card (no 3DS) |
Per docs.adyen.com/development-resources/testing/3d-secure:
| Card | Behaviour |
|---|---|
| 4917 6100 0000 0000 | 3DS 2 challenge flow |
| 5454 5454 5454 5454 | 3DS 2 frictionless |
| 4012 8888 8888 1881 | 3DS 1 (deprecated; for migration testing) |
Per developer.paypal.com/braintree/docs/guides/3d-secure/testing-go-live:
| Card | Behaviour |
|---|---|
| 4000 0000 0000 1091 | Authenticate via standard flow |
| 4000 0000 0000 1109 | Frictionless |
| 4000 0000 0000 1125 | Bypass (skipped) |
1. Merchant calls Authorize / PaymentIntent / Charge
2. Gateway returns "requires_action" / "RedirectShopper" / "PAYER_ACTION_REQUIRED"
per payment-flow-states-reference
3. Frontend redirects user to issuer-hosted 3DS challenge
4. User completes challenge (or auto-completes for frictionless)
5. Redirect back to merchant return URL
6. Merchant confirms PaymentIntent (or equivalent)
7. Gateway returns final state (succeeded / failed)
Test surface per step:
| Step | Test |
|---|---|
| 1 | Initiate with each test card; assert state |
| 2 | Frontend handles each gateway's "requires action" key correctly |
| 3 | Redirect URL is built with the gateway-provided client secret / token |
| 4 | Issuer-hosted page is reachable (manual + Playwright e2e) |
| 5 | Return URL handler parses the response correctly |
| 6 | Confirm call is idempotent (per payment-flow-states-reference) |
| 7 | Final state matches expected per test card |
test('frictionless authentication', async () => {
// Card 4000 0000 0000 3055 in Stripe test mode
const intent = await stripe.paymentIntents.create({
amount: 1000, currency: 'eur',
payment_method: 'pm_card_threeDSecure2Supported',
confirm: true,
});
expect(intent.status).toBe('succeeded'); // No challenge needed
});
test('challenge flow', async () => {
// Card 4000 0027 6000 3184
const intent = await stripe.paymentIntents.create({
amount: 1000, currency: 'eur',
payment_method: 'pm_card_threeDSecure2Required',
confirm: true,
return_url: 'https://example.com/return',
});
expect(intent.status).toBe('requires_action');
expect(intent.next_action.type).toBe('redirect_to_url');
});
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Skip 3DS in tests | EU regulations require it; you'll discover broken at launch | Per-gateway 3DS card battery |
| Test only happy path | Challenge failure path also matters | Test failure + cancel mid-challenge |
| No return-URL handler test | Race conditions on redirect-back lost | Test the full round-trip |
| Hardcoded redirect URL in production code | Localhost in prod | Per-environment config |
Treat requires_action as failure | It's the normal mid-flow state | Handle explicitly |
| 3DS 1 still in code paths | Deprecated since 2022 | Remove and test |
| One test for all gateways | Each handles 3DS slightly differently | Per-gateway |
| Sync expectation on async flow | Confirm is async | Wait for webhook |
payment-flow-states-reference,
pci-dss-scope-reference.stripe-test-cards-and-webhooks,
adyen-test-mode,
braintree-test-cards.Provides 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.
npx claudepluginhub testland/qa --plugin qa-payment