Skip to content

Take payments through Paygate, and show the plans on the site (FW-607) - #46

Merged
ashutosh091 merged 3 commits into
previewfrom
feat/paygate-billing
Nov 14, 2026
Merged

ashutosh091 merged 3 commits into
previewfrom
feat/paygate-billing

Conversation

@ashutosh091

Copy link
Copy Markdown
Contributor

What

Floodwall Cloud can now sell plans. Payments go through Paygate (pay.erpai.studio), our own Stripe hub, so the Worker never handles a card or a Stripe key. The plans are also shown on the landing page, and /pricing is now public.

Plans (base fee + overage)

Plan Price Decisions included / month Extra decisions
Self-host Free (MIT) Unlimited —
Free $0 25,000 —
Team $49/mo or $490/yr 250,000 $0.30 per 1k
Business $399/mo or $3,990/yr 2.5M $0.20 per 1k
Enterprise Contact us Custom Custom

The extra-decision charge waits for a hosted plane to count decisions (FW-701 / FW-704). Until then, the plans bill their base price.

api/

  • GET /billing, POST /billing/checkout, POST /billing/manage (Paygate's hosted billing page), and POST /billing/webhook.
  • The webhook checks Paygate's HMAC-SHA256 signature (5-minute tolerance) and skips event ids it has already handled. An older event never overwrites a newer plan, and payment.failed emails the owner.
  • Migration 0003_billing.sql: plan columns on users (every account starts on free) and billing_events, which the daily sweep clears after a week.
  • New PAYGATE_URL var and PAYGATE_SECRET_KEY / PAYGATE_WEBHOOK_SECRET secrets. Documented in api/README.md.

site/

  • /pricing shows the new plans. Signed in, it marks your plan and offers Move to Team / Business.
  • /account gets a Plan and billing section: plan, renewal or end date, upgrade, and invoices/card/cancel.
  • The landing page gets a Pricing section, and the footer links to it.
  • /pricing drops noindex and joins the sitemap. The build writes pricing/index.html, so GitHub Pages answers it with 200 instead of 404.

TESTING.md

How to test the crate, the Worker and the site locally, run billing end to end through a tunnel, and smoke-test on preview.floodwall.ai.

Testing

  • api: npm run check passes 71 tests, 14 of them new for billing (checkout, billing page, signatures, replays, out-of-order webhooks), with Paygate stood in.
  • site: npm test passes 24 tests, and the production build works.
  • Checked in the browser against a mock of the service: Free, Team and demo states on /account and /pricing, plus the landing section at desktop and phone widths.
  • Not yet tested against real Paygate: the floodwall app and its keys don't exist there yet.

Before deploying

  1. In Paygate (test mode), create app floodwall with plans team and business, the features JSON from api/README.md, and webhook URL https://api.floodwall.ai/billing/webhook.
  2. npx wrangler secret put PAYGATE_SECRET_KEY and npx wrangler secret put PAYGATE_WEBHOOK_SECRET.
  3. In api/, run npm run migrate:remote, then npm run deploy.
  4. Before live keys: Terms, Privacy and Refund pages (FW-605), which Stripe requires.

Plans are sold through Paygate (pay.erpai.studio), our Stripe hub, so the
Worker never handles a card or a Stripe key.

- api: /billing, /billing/checkout, /billing/manage and a signed
  /billing/webhook. Webhooks are checked (HMAC-SHA256, 5-minute
  tolerance), deduplicated by event id, and an older event never
  overwrites a newer plan. payment.failed emails the owner.
- api: migration 0003 adds the plan columns (every account starts on
  Free) and billing_events, swept after a week.
- site: pricing moves to a base fee plus overage (Free, Team $49,
  Business $399); signed-in accounts can buy from /pricing and get a
  Plan and billing section on /account.
- Tests for checkout, the billing page, signatures, replays and
  out-of-order webhooks, with Paygate stood in. TESTING.md covers local
  and live testing.
- A Pricing section between Roadmap and Waitlist: the same plan rail as
  /pricing, Enterprise, and a link to compare every plan. It is
  prerendered with the rest of the landing page.
- The header's Pricing link follows the section while it is in view; the
  footer lists Pricing.
- /pricing drops its noindex tag and joins the sitemap, and the build
  writes pricing/index.html so GitHub Pages answers it with 200, not 404.
@ashutosh091
ashutosh091 merged commit 9430e80 into preview Nov 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant