← Documentation home

Canonical Markdown source · Oct 20, 2018

Stripe sandbox payments

ops/stripe-sandbox-payments.md · 36 lines · SHA-256 ea888060b195

Status

Meta Museum uses Stripe-hosted Checkout Sessions for the initial one-time supporter flow. The connected sandbox belongs to Sun & Rain Works, the sole-proprietorship operator of the Meta Museum project. Production checkout remains disabled until live-account activation, offer/refund approval, webhook evidence retention, and production secrets are completed.

Architecture

  • `POST /api/stripe/checkout` accepts a bounded whole-dollar amount ($5–$500) and creates a one-time hosted Checkout Session.
  • Only `sk_test_` keys are accepted by the application route. This prevents accidental live charges during sandbox development.
  • Stripe receives all payment details; Meta Museum never receives card numbers.
  • Offer and revision metadata are attached to both the Session and PaymentIntent.
  • `POST /api/stripe/webhook` verifies Stripe's signature over the unchanged raw request body with a five-minute tolerance, then writes to Postgres transactionally.
  • `stripe_webhook_events` enforces unique provider event IDs; a reused ID with different evidence fails as a collision.
  • A transaction-scoped advisory lock serializes concurrent events for the same PaymentIntent before the receipt projection is rebuilt.
  • `stripe_payment_receipts` retains the deterministic lifecycle projection and its reproducible SHA-256 receipt.
  • Checkout, PaymentIntent, charge, asynchronous failure/success, refund, and dispute events reduce in provider-created order, so delivery order does not control the result.
  • Amount, currency, offer/revision, payment status, refunds, disputes, settlement, and observed Stripe fees remain explicit. An unobserved fee is `null`, never silently zero.
  • Every `livemode: false` receipt is permanently `excluded-test-mode` with `verifiedRevenueMinor: 0`.
  • The revenue adapter emits no event for sandbox, unsettled, refunded, disputed, non-USD, unlinked, or incomplete evidence. It also requires the independently retained checkout-start event ID.

Local verification

Add sandbox credentials and the Stripe CLI-provided signing secret to `.env`. Never commit either value. The repeatable proof flow is:


pnpm revenue:stripe:sandbox-delivery-proof
pnpm revenue:stripe:sandbox-receipt-verify

The first command opens a bounded five-minute listener window and creates a real hosted sandbox Checkout Session. Complete that Session with a Stripe test card. It retains a non-secret delivery proof only after HTTP 200 delivery, Postgres persistence, receipt hashing, and test-revenue exclusion succeed. The second command runs in a fresh process and reproduces the stored receipt hash, checks event-ID uniqueness, and retains restart evidence.

Recovery: redeliver the same Stripe event. The event primary key makes it a no-op when its payload digest matches; a digest mismatch raises a collision error. If projection fails, Stripe receives HTTP 500 and retries. Never delete an event row to force reprocessing; repair the reducer and deterministically rebuild the receipt from retained normalized events.

Production requires a separate reviewed change: live keys in encrypted hosting configuration, a production webhook endpoint registration, durable idempotent event receipts, refunds/disputes, approved public terms, and a live-mode smoke test that does not count self-funding or test transactions as revenue.