CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/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.

72

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Medium

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

replay-simulators.mdreferences/

Gateway-native replay simulators + signature schemes

Deep reference for payment-flow-test-author SKILL.md Phase 3. The per-gateway mechanics for driving and replaying real webhook events, and the signature-verification gauntlet each gateway needs.

Replay simulators

Each gateway ships a way to (re)send a real event at the handler:

GatewayReplayNotes
Stripestripe trigger payment_intent.succeeded; stripe events resend evt_test_12345CLI (stripe-cli); resend covers a 30-day window (cli/events/resend)
AdyenCustomer Area transaction "Resend webhook"Re-sends with the original signature - useful for idempotency tests
PayPalDashboard simulator; REST equivalent via the APIsimulate-event
Braintreegateway.webhookTesting.sampleNotification(kind, id)Generates a test signature for any event kind (parse/node)

Signature schemes

The scheme differs per gateway; run the same four-case gauntlet against each:

GatewaySignature schemeReference
StripeHMAC-SHA256 over the payload; the signature carries a timestamp, so old timestamps rejectwebhooks/signatures
AdyenHMAC-SHA256 over the canonical string; validated per-event, not per-requestverify-hmac-signatures
PayPalSHA256-with-RSA; verify via the PayPal verification endpoint or SDK helperwebhooks/rest
Braintreeparser validates bt_signature against the merchant's public keyBraintree webhook parser

The four-case signature gauntlet (Stripe form)

Per docs.stripe.com/webhooks/signatures, the gauntlet is four cases - unsigned, wrong-secret, valid, and expired-timestamp:

const payload = JSON.stringify({ type: 'payment_intent.succeeded' });

test('rejects unsigned payload', async () => {
  const res = await fetch('/webhooks/stripe', { method: 'POST', body: payload });
  expect(res.status).toBe(401);  // No Stripe-Signature header
});

test('rejects wrong-secret signature', async () => {
  const wrongSig = stripe.webhooks.generateTestHeaderString({ payload, secret: 'wrong-secret' });
  const res = await fetch('/webhooks/stripe', {
    method: 'POST', body: payload, headers: { 'stripe-signature': wrongSig },
  });
  expect(res.status).toBe(401);
});

test('accepts valid signature', async () => {
  const sig = stripe.webhooks.generateTestHeaderString({
    payload, secret: process.env.STRIPE_WEBHOOK_SECRET!,
  });
  const res = await fetch('/webhooks/stripe', {
    method: 'POST', body: payload, headers: { 'stripe-signature': sig },
  });
  expect(res.status).toBe(200);
});

test('rejects expired timestamp', async () => {
  const oldSig = stripe.webhooks.generateTestHeaderString({
    payload,
    secret: process.env.STRIPE_WEBHOOK_SECRET!,
    timestamp: Math.floor(Date.now()/1000) - 3600,  // 1 hour ago
  });
  const res = await fetch('/webhooks/stripe', {
    method: 'POST', body: payload, headers: { 'stripe-signature': oldSig },
  });
  expect(res.status).toBe(401);
});

Gotcha: the handler must read the raw request body - a parsed-then-restringified copy fails signature verification on the restringified bytes.

Sources

SKILL.md

tile.json