Cross-gateway, protocol-level reference for 3-D Secure (3DS 2.x) test coverage. Covers the EMVCo frictionless / challenge / not-applicable flow paths, SCA under EU PSD2, and the per-PAN test cards for Stripe, Adyen, and Braintree. The gateway-specific wrappers (adyen-test-mode / stripe-test-cards-and-webhooks / braintree-test-cards) compose this skill for single-gateway work, so use one of those for a single-gateway query. Use this skill when designing multi-gateway 3DS test coverage, auditing a 3DS redirect round-trip, or investigating a challenge-flow regression that is not gateway-specific.
70
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Medium
Suggest reviewing before use
3-D Secure (3DS) is the EMVCo-specified card-authentication protocol. 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.
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-factor SCA (two of knowledge / possession / inherence). Exemptions: low-value (< €30), recurring, trusted-beneficiary, merchant-initiated - each with tracking requirements.
Per-PAN 3DS test cards for Stripe, Adyen, and Braintree, each with its source
doc, are in references/gateway-test-cards.md.
The core Stripe cards used in the assertions below: 4000 0000 0000 3055
(frictionless) and 4000 0027 6000 3184 (challenge).
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.