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 the 3DS flows reference in payment-flow-states-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.
Install with skills.sh (any agent)
npx skills add testland/qa --skill stripe-test-cards-and-webhooksstripe-test-cards-and-webhooks
Overview
Stripe's test-mode is feature-complete: every API call works against test data, no real money moves. Per docs.stripe.com/testing (opens in new window), test keys (sk_test_* / pk_test_*) accept canonical test cards that deterministically produce success / decline / 3DS challenge.
When to use
Authoring
Install
npm install stripe
pip install stripeTest keys come from the Stripe Dashboard (Developers → API keys → toggle to "View test data").
Initialize
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_TEST_KEY!);Canonical test cards
Per docs.stripe.com/testing (opens in new window):
| Card | Behaviour |
|---|---|
| 4242 4242 4242 4242 | Success (Visa) |
| 5555 5555 5555 4444 | Success (Mastercard) |
| 4000 0000 0000 0002 | Decline (generic_decline) |
| 4000 0000 0000 9995 | Decline (insufficient_funds) |
| 4000 0000 0000 9987 | Decline (lost_card) |
| 4000 0000 0000 0069 | Expired card |
| 4000 0027 6000 3184 | 3DS authentication required (challenge) |
| 4000 0000 0000 3055 | 3DS supported but frictionless |
| 4100 0000 0000 0019 | Fraud-prevention block |
Test PAN any future expiry; any 3-digit CVC.
PaymentIntent end-to-end
test('successful payment via 4242', async () => {
const intent = await stripe.paymentIntents.create({
amount: 1000,
currency: 'usd',
payment_method: 'pm_card_visa',
confirm: true,
return_url: 'https://example.com/return',
});
expect(intent.status).toBe('succeeded');
});
test('declined payment', async () => {
await expect(
stripe.paymentIntents.create({
amount: 1000,
currency: 'usd',
payment_method: 'pm_card_chargeDeclined',
confirm: true,
return_url: 'https://example.com/return',
})
).rejects.toThrow(/card was declined/);
});Webhook handler test
import { buffer } from 'micro';
import handler from './stripe-webhook';
test('webhook handler validates signature', async () => {
const payload = JSON.stringify({ type: 'payment_intent.succeeded', data: {...} });
const signature = stripe.webhooks.generateTestHeaderString({
payload,
secret: process.env.STRIPE_WEBHOOK_SECRET!,
});
const req = { headers: { 'stripe-signature': signature }, body: Buffer.from(payload) };
const res = await handler(req as any);
expect(res.statusCode).toBe(200);
});Per docs.stripe.com/webhooks/signatures (opens in new window): the Stripe CLI's generateTestHeaderString produces a valid HMAC-SHA256 signature for testing.
Stripe CLI local forwarding
Per docs.stripe.com/stripe-cli (opens in new window):
stripe listen --forward-to http://localhost:3000/webhooks/stripe
# Forwards real Stripe webhook events to your local endpointstripe trigger payment_intent.succeeded
# Sends a synthetic event to test your handlerThe CLI also exposes a signing secret (different from your prod webhook secret) for local testing.
Idempotency
test('idempotency key prevents duplicate charges', async () => {
const key = 'order-12345';
const r1 = await stripe.paymentIntents.create({...}, { idempotencyKey: key });
const r2 = await stripe.paymentIntents.create({...}, { idempotencyKey: key });
expect(r1.id).toBe(r2.id); // Same PaymentIntent
});Running
npm testFor webhook-integration tests with stripe listen running:
stripe listen --forward-to http://localhost:3000/webhooks/stripe &
npm testCI integration
jobs:
stripe-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
- run: npm ci && npm test
env:
STRIPE_TEST_KEY: ${{ secrets.STRIPE_TEST_KEY }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }}Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Mocking Stripe SDK directly | Loses signature verification, error mapping | Test against real test-mode API |
| Hardcoded test cards in many tests | Update breakage when Stripe changes | Use named constants for the card PANs |
| Skip webhook signature verification | Webhook replay attack | Always verify |
| Tests without idempotency key | Retried tests duplicate-create | Always set idempotency |
| Mix prod + test keys | Real money risk | Strict per-env key separation |
| Test webhook handler without raw body | body-parser strips bytes; signature mismatch | Use raw body middleware |
| One-off webhook event tests | Per-event-type integration coverage missed | Test the full event matrix relevant to your flow |
| Long-running stripe listen in CI | Hangs builds | Use stripe listen --print-secret then background |
Limitations
References
Related skills
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, refund / dispute / chargeback transitions, and the 3-D Secure (EMVCo 3DS 2.x) frictionless / challenge flow paths with per-gateway 3DS test cards (references/3ds-flows.md). Use when designing tests for payment flows, auditing state-handling code, or covering a 3DS round-trip; this is the state model, not a builder - to author suites on it use payment-flow-test-author (refunds, disputes, webhook replay).
payment-flow-test-author
Build-an-X workflow that authors the full payment-flow test suite in three phases: the refund matrix (full / partial / multiple-partials / over-refund / already-refunded, per-gateway APIs for Stripe, Adyen, PayPal, Braintree), the chargeback / dispute suite (Visa + Mastercard reason codes, evidence submission windows, won / lost / accepted dispositions), and webhook replay + recovery via gateway-native simulators (Stripe CLI trigger / resend, Adyen Customer Area resend, PayPal Webhook Simulator, Braintree sampleNotification). Driven by the state model in payment-flow-states-reference. Use when building refund, dispute, or payment-webhook-robustness coverage for a payment integration; for generic (non-payment) webhook receiver testing use webhook-delivery-tester in the qa-notifications plugin.
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).
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-flow-test-author (idempotency + replay robustness). Does not cover single-event CLI replay or handler idempotency testing (see payment-flow-test-author for those). Use when authoring tests for subscription or recurring-billing integrations.