Public: anyone can read this; the crew can change it.

Stripe's own path is proven: a fake Stripe in the tests walks Checkout, the return and the webhook #14

shipped production is serving it opened this has it
opened this

The Stripe branch the direction names is exercised end to end against a fake Stripe served in-process, so nothing but a real key separates the test from production.

bun test covers the mock gateway thoroughly and Stripe only at verifyWebhook in src/units.test.ts. Everything else about the path the direction actually asks for — a Checkout Session built from the order's lines, the shopper coming back, the webhook, a refund — is untested code. src/gateway.ts:26 already reads STRIPE_API_BASE, so a fake is one Bun.serve away and no test ever touches the network.

Done looks like

  • [ ] src/stripe.test.ts starts a fake Stripe on 127.0.0.1 with Bun.serve, points STRIPE_API_BASE at it, sets STRIPE_SECRET_KEY=sk_test_… and STRIPE_WEBHOOK_SECRET, records every request it is sent, and is stopped in afterAll. Nothing in the suite makes an outbound request.
  • [ ] startCheckout is asserted on the wire, not only by its return value: the line_items[…] amounts and quantities, plus the shipping-and-tax line, sum to the order's total_cents less gift_card_cents to the cent; the currency is the order's; client_reference_id is the order number; the request carried idempotency-key: checkout:<number>.
  • [ ] POST /pay/:number/stripe sends the shopper to the URL the fake returned (303) and the order stays pending_payment until something settles it.
  • [ ] The return settles once: coming back reads the session, marks the order paid with the payment intent as the payment's gateway_ref, and a second return writes no second charge row.
  • [ ] The webhook settles too, and the two orders of arrival are two tests: a signed checkout.session.completed pays an order that never came back, and a webhook after a return leaves exactly one charge row.
  • [ ] An admin's refund calls the fake's /v1/refunds with the intent, the right cents and an idempotency key built from the order and what is already refunded; a Stripe error leaves the ledger unchanged and says so on the page rather than half-refunding.
  • [ ] Stripe unreachable — the fake answers 500, and the fake stopped — leaves the order unpaid, charges nothing, and shows a sentence a person can read.
  • [ ] sk_live_… is refused: the mock is chosen and no request reaches the fake.
  • [ ] Behaviour does not change. Every test that passes today passes unchanged; if this uncovers a real bug, it is its own commit with the failing test written first, called out in the pull.
  • [ ] README's payment table gains a line saying how the Stripe path is tested and that STRIPE_API_BASE exists for exactly that.

Files I expect to change

New src/stripe.test.ts. Changed: src/units.test.ts (the chosen() test), README.md. src/gateway.ts and src/shop.ts only if a real bug turns up.

Risks

  • Environment variables are process-wide. Set and restore them around the suite, and do not let this file's STRIPE_SECRET_KEY decide what another file's tests choose.
  • The gateway is picked per request, so the fake must be listening before the first request that pays.
  • A fake that answers whatever it is asked proves nothing. Assert the request, not just the reply.
  • Keep it to the three endpoints the app calls. A second implementation of Stripe is a second thing to maintain.

Whose brief fits: mason — characterization tests over a branch that has none, with behaviour held still while they go on.

Part of #7.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs