Stripe operator setup

Do this once per environment (test vs live). Customers click Subscribe in the dashboard; Stripe calls our HTTPS webhook to attach subscription state to the tenant ID.

Widget embed (no Stripe): customers complete Website setup at the top of /app so their live https:// origin is allowlisted for sign-in — they do not configure Google or Turnstile themselves.

Stripe Connect (same Worker): sign in, then open Connect setup for the V2 Connect flow (onboarding, products, storefront, thin + subscription webhooks). The operator dashboard SPA at /app does not embed this flow yet — it is a separate page served by the Worker.

One Price ID per tier. Checkout sends your selected tier to the Worker, which loads a different Stripe price_… for Starter, Pro, and Business. If every Worker secret points at the same Price (for example only the $99 Business price), Stripe will always show $99/mo no matter which radio the customer picks. Use three distinct recurring prices from Stripe Products.

Public planCheckout tier / dashboard radioWorker secret (paste your Stripe Price ID)
Starter — $9/mostarterSTRIPE_PRICE_STARTER_IDprice_1TW1Or7W05iPLYSvorue2Fdj
Pro — $29/moproSTRIPE_PRICE_PRO_ID (or legacy STRIPE_PRICE_ID) → price_1TW1Or7W05iPLYSv5Vg4ErKE
Business — $99/mobusinessSTRIPE_PRICE_BUSINESS_IDprice_1TW1Os7W05iPLYSvG4YEmF4L

If you fork CapchaCloud or use a different Stripe account, replace the price_… values with your own from Stripe → Products → Price. The Worker still reads them from secrets (npx wrangler secret put …); nothing in git pushes them to Cloudflare automatically.

  1. Apply database migrations (includes billing + auth credit columns). From trust-engine:
    npx wrangler d1 migrations apply consent-metadata --remote
    Use your database name from wrangler.jsonc if different.
  2. Create Products & recurring Prices in Stripe Dashboard (Products → Add product → recurring). Copy each Price ID (price_…) for the tiers you sell publicly — e.g. Starter ($9), Pro ($29), Business ($99) — and map them to STRIPE_PRICE_STARTER_ID, STRIPE_PRICE_PRO_ID (or legacy STRIPE_PRICE_ID), and STRIPE_PRICE_BUSINESS_ID.
  3. Add Worker secrets (never commit keys). From trust-engine:
    npx wrangler secret put STRIPE_SECRET_KEY
    npx wrangler secret put STRIPE_WEBHOOK_SECRET
    npx wrangler secret put STRIPE_PRICE_STARTER_ID
    npx wrangler secret put STRIPE_PRICE_PRO_ID
    npx wrangler secret put STRIPE_PRICE_BUSINESS_ID
    npx wrangler secret put STRIPE_PRICE_ID

    The last line is optional: STRIPE_PRICE_ID is a Pro-only legacy default when STRIPE_PRICE_PRO_ID is unset. Starter and Business never read STRIPE_PRICE_ID.

    Or run scripts\set-stripe-secrets.ps1 (interactive). Non-interactive: set $env:STRIPE_* then scripts\push-stripe-secrets-from-env.ps1. Test keys sk_test_; live sk_live_.

    STRIPE_PRICE_ID is stored as a secret so it isn't exposed to the browser. It is the Pro default when STRIPE_PRICE_PRO_ID is unset. For Starter and Business checkout, set STRIPE_PRICE_STARTER_ID and STRIPE_PRICE_BUSINESS_ID separately — they do not fall back to STRIPE_PRICE_ID (that mismatch used to charge the wrong tier). Redeploy after changing secrets.

  4. Webhook endpoint — Stripe Dashboard → Developers → Webhooks → Add endpoint.
    URL: https://YOUR_DOMAIN/webhooks/stripe (e.g. https://capchacloud.com/webhooks/stripe).
    Events to send: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted.
    Reveal the signing secret (whsec_…) and set it as STRIPE_WEBHOOK_SECRET.
  5. Customer Portal (optional but recommended): Stripe Dashboard → Settings → Billing → Customer portal → Activate. Lets users manage cards and invoices after checkout.
  6. Deploy Worker:
    npx wrangler deploy --env "" --keep-vars
  7. Local webhook testing — install Stripe CLI, then:
    stripe login
    stripe listen --forward-to http://127.0.0.1:8787/webhooks/stripe
    Copy the CLI webhook signing secret into .dev.vars as STRIPE_WEBHOOK_SECRET while developing.

Verify: sign in → Trust & billing → Subscribe → complete Checkout → billing status should become active after webhook delivery (refresh).