3ds-test-flow-reference
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.
Install with skills.sh (any agent)
npx skills add testland/qa --skill 3ds-test-flow-reference3ds-test-flow-reference
Overview
3-D Secure (3DS) is the EMVCo-specified card-authentication protocol. Per emvco.com (opens in new window), 3DS 2.x is the current spec (cite by stable ID: EMV 3-D Secure Protocol Specification v2.x); 3DS 1.0 is deprecated.
When to use
The three flow outcomes
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.
SCA under PSD2
Per European Banking Authority RTS on SCA (opens in new window): 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-gateway test cards
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).
Test flow surface
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 |
Frictionless vs challenge - testable assertions
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-patterns
| 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 |
Limitations
References
Gateway 3DS test cards
View source (opens in new window)Gateway 3DS test cards
Per-PAN test cards that drive 3DS challenge, frictionless, and bypass flows in each gateway's test mode. Cross-referenced from 3ds-test-flow-reference. For single-gateway work, prefer the gateway wrapper skills (stripe-test-cards-and-webhooks, adyen-test-mode, braintree-test-cards).
Stripe
Per docs.stripe.com/testing#regulatory-cards (opens in new window):
| 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) |
Adyen
| 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) |
Braintree
Per developer.paypal.com/braintree/docs/guides/3d-secure/testing/node (opens in new window):
| Card | Behaviour |
|---|---|
| 4000 0000 0000 1091 | Authenticate via standard flow |
| 4000 0000 0000 1109 | Frictionless |
| 4000 0000 0000 1125 | Bypass (skipped) |
Related skills
adyen-test-mode
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.
braintree-test-cards
Wraps Braintree (PayPal-owned) sandbox testing patterns: sandbox merchant credentials, Drop-in / Hosted Fields client-side patterns, the Transaction lifecycle (submitted_for_settlement → settled), Braintree's distinctive test-card behaviours (specific PANs trigger specific errors), and the webhook verification (Braintree Webhook Parser). Use when testing Braintree-integrated code.
chargeback-flow-test-author
Workflow-driven skill that builds the chargeback / dispute test suite: canonical reason codes (Visa 10.4 fraud, 13.1 services-not-provided; Mastercard MCC 4855), per-gateway dispute APIs (Stripe Disputes, Adyen Chargeback notifications, PayPal Disputes), the evidence-submission flow + window, and disposition outcomes (won / lost / accepted). Use when designing dispute coverage; for refunds use refund-test-matrix-builder, for webhook redelivery + idempotency use payment-webhook-replay, and for the lifecycle state model use payment-flow-states-reference.
payment-flow-states-reference
Pure-reference catalog of payment lifecycle state machines across Stripe, Adyen, PayPal, and Braintree: canonical states (created / requires_action / processing / succeeded / requires_capture / canceled / failed), authorisation vs capture, asynchronous webhook states, and refund / dispute / chargeback transitions. Use when designing tests for payment flows or auditing state-handling code; this is the state model, not a builder - to author suites on it use refund-test-matrix-builder (refunds), chargeback-flow-test-author (disputes), or payment-webhook-replay (webhook replay).
payment-webhook-replay
Workflow-driven skill that builds payment webhook replay + recovery tests. Covers the idempotency contract (every webhook handler must handle redelivery without side effects), the replay simulators (Stripe CLI `stripe trigger`, Adyen Customer Area resend, PayPal Webhook Simulator, Braintree webhook test), the signature-verification gauntlet (HMAC-SHA256 per gateway, expired-timestamps rejection), and the partial-failure recovery scenarios. Use when designing webhook robustness tests.
paypal-sandbox
Wraps PayPal Sandbox testing patterns: sandbox account creation (Business + Personal accounts in developer.paypal.com), the Orders v2 API (create / capture / refund), webhook event simulator (developer.paypal.com webhook simulator), sandbox-account-specific test cards, and the OAuth2 client-credentials flow for sandbox. Use when testing PayPal-integrated code.
pci-dss-scope-reference
Pure-reference catalog of PCI DSS v4.0 scope reduction techniques + the testable scope boundaries. Covers the SAQ levels (A through D, picked by how cardholder data flows), the PAN-storage prohibitions (only first-6 + last-4 retained; nothing else cleartext), the tokenization + hosted-fields scope-reduction patterns (Stripe Elements / Adyen Drop-in / Braintree Hosted Fields keep PAN off your servers), Network-Segmentation as PCI scope-reduction, and the testable behaviours the scope boundary creates. This is the catalog of WHY the boundary matters and what it makes testable - not a checker that verifies a given integration against the standard. Use when designing or auditing the PCI scope of a payment integration.
refund-test-matrix-builder
Workflow-driven skill that builds a refund test matrix from a payment-flow inventory: refund variants (full / partial / multiple-partials / over-refund / refund-on-disputed / refund-on-already-refunded), per-gateway nuances (Stripe, Adyen, PayPal, Braintree refund APIs), and timing variants (immediate / next-day / declined-by-bank), one test case per cell. Use for refund coverage on a new integration; for chargeback / dispute coverage use chargeback-flow-test-author, for webhook redelivery + idempotency use payment-webhook-replay, and for the state model use payment-flow-states-reference.
stripe-subscription-billing-test-author
Builds test suites for Stripe recurring-billing flows: trial-to-paid conversion, proration on plan upgrade and downgrade, dunning on failed renewal, cancel and reactivation, and the full subscription webhook event matrix (invoice.payment_failed, customer.subscription.updated, customer.subscription.deleted, invoice.paid). Uses Stripe Billing test clocks (POST /v1/test_helpers/test_clocks) to time-travel through billing cycles without calendar delay. Distinct from stripe-test-cards-and-webhooks (one-time PaymentIntents) and payment-webhook-replay (idempotency + replay robustness). Does not cover single-event CLI replay or handler idempotency testing (see payment-webhook-replay for those). Use when authoring tests for subscription or recurring-billing integrations.
stripe-test-cards-and-webhooks
Wraps Stripe API testing patterns: test-mode initialization, the canonical test cards (4242 success; 4000 0000 0000 0002 declined; 4000 0027 6000 3184 3DS challenge per 3ds-test-flow-reference), the Stripe CLI webhook flow (`stripe listen --forward-to`), the Stripe CLI fixture commands (`stripe trigger payment_intent.succeeded`), and the webhook signature verification (Stripe-Signature header + HMAC-SHA256). Use when testing Stripe-integrated code.