Context
Paddle rejected our application for milestone 5.2 (payment infrastructure). We need an alternative payment provider. Stripe is the recommended path — no approval gate, mature Go SDK, and our DB schema is already prepared.
Current state
plans table exists with tiers and daily page quotas (supabase/migrations/20260104212025_add_daily_usage_quotas.sql)
organisations.plan_id already references plans, defaults to free tier
- Zero payment code — no SDK, no webhooks, no checkout flow in Go
- Quota enforcement is handled server-side via SQL functions — payment provider just needs to flip
plan_id
Proposed: Stripe + Stripe Checkout + Stripe Tax
Why Stripe over alternatives
| Option |
Type |
Fees |
Approval |
Tax handling |
Go SDK |
| Stripe |
Direct |
~2.9% + 30c |
None |
Stripe Tax add-on |
Mature |
| Paddle |
MoR |
~5-8% |
Rejected |
Included |
OK |
| Lemon Squeezy |
MoR |
~5% + 50c |
Easier |
Included |
Limited |
| Polar.sh |
MoR |
~5% |
Easy |
Included |
Early |
Stripe gives us the lowest fees, no approval blocker, and the most mature Go integration. Stripe Tax can be added later to handle GST/VAT collection automatically if we sell internationally at scale.
Implementation scope
1. Stripe SDK + configuration
- Add
github.com/stripe/stripe-go to go.mod
- Add
STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET to env config
- Add
STRIPE_PUBLISHABLE_KEY for frontend checkout redirect
- Update
.env.example and docs/operations/ENV_VARS.md
2. Checkout session endpoint
POST /v1/checkout — creates a Stripe Checkout Session for a given plan
- Redirects user to Stripe's hosted payment page
- Success/cancel URLs route back to
/settings
- Maps our
plans table rows to Stripe Price IDs (store mapping in config or DB)
3. Customer portal endpoint
POST /v1/billing/portal — creates a Stripe Billing Portal session
- Lets users manage subscription, update payment method, cancel
- Zero UI to build on our side
4. Webhook handler
POST /webhooks/stripe — receives Stripe events
- Key events to handle:
checkout.session.completed → set org plan_id to purchased plan
customer.subscription.updated → handle plan changes/downgrades
customer.subscription.deleted → revert org to free plan
invoice.payment_failed → flag org, optionally notify
- Verify webhook signature using
STRIPE_WEBHOOK_SECRET
5. Frontend integration
- Add "Upgrade" / "Manage billing" buttons to
settings.html
- "Upgrade" calls checkout endpoint, redirects to Stripe
- "Manage billing" calls portal endpoint, redirects to Stripe
- No card forms or payment UI to build — Stripe hosts everything
6. Plan mapping
- Create Stripe Products + Prices matching our
plans table tiers
- Store Stripe Price ID ↔ plan mapping (either in
plans table as a stripe_price_id column, or in config)
- Migration to add
stripe_price_id and stripe_customer_id columns
7. Stripe Tax (future, optional)
- Enable Stripe Tax on checkout sessions to auto-calculate GST/VAT
- Requires setting our tax registration in the Stripe dashboard
- No code changes beyond
automatic_tax: {enabled: true} on session creation
Files likely touched
cmd/app/main.go — route registration
internal/api/handlers.go — new checkout/portal/webhook handlers
internal/api/middleware.go — exempt webhook route from CSRF/auth
internal/db/organisations.go — update plan on webhook events
settings.html + web/static/app/pages/settings.js — billing UI buttons
supabase/migrations/ — add stripe_price_id, stripe_customer_id columns
.env.example, docs/operations/ENV_VARS.md — new env vars
Dockerfile — no changes needed (Go binary, no new static assets)
Effort estimate
~1-2 days for core flow (checkout + webhooks + settings UI). Stripe Checkout and Billing Portal eliminate most of the frontend work.
Open questions
Context
Paddle rejected our application for milestone 5.2 (payment infrastructure). We need an alternative payment provider. Stripe is the recommended path — no approval gate, mature Go SDK, and our DB schema is already prepared.
Current state
planstable exists with tiers and daily page quotas (supabase/migrations/20260104212025_add_daily_usage_quotas.sql)organisations.plan_idalready references plans, defaults to free tierplan_idProposed: Stripe + Stripe Checkout + Stripe Tax
Why Stripe over alternatives
Stripe gives us the lowest fees, no approval blocker, and the most mature Go integration. Stripe Tax can be added later to handle GST/VAT collection automatically if we sell internationally at scale.
Implementation scope
1. Stripe SDK + configuration
github.com/stripe/stripe-gotogo.modSTRIPE_SECRET_KEYandSTRIPE_WEBHOOK_SECRETto env configSTRIPE_PUBLISHABLE_KEYfor frontend checkout redirect.env.exampleanddocs/operations/ENV_VARS.md2. Checkout session endpoint
POST /v1/checkout— creates a Stripe Checkout Session for a given plan/settingsplanstable rows to Stripe Price IDs (store mapping in config or DB)3. Customer portal endpoint
POST /v1/billing/portal— creates a Stripe Billing Portal session4. Webhook handler
POST /webhooks/stripe— receives Stripe eventscheckout.session.completed→ set orgplan_idto purchased plancustomer.subscription.updated→ handle plan changes/downgradescustomer.subscription.deleted→ revert org to free planinvoice.payment_failed→ flag org, optionally notifySTRIPE_WEBHOOK_SECRET5. Frontend integration
settings.html6. Plan mapping
planstable tiersplanstable as astripe_price_idcolumn, or in config)stripe_price_idandstripe_customer_idcolumns7. Stripe Tax (future, optional)
automatic_tax: {enabled: true}on session creationFiles likely touched
cmd/app/main.go— route registrationinternal/api/handlers.go— new checkout/portal/webhook handlersinternal/api/middleware.go— exempt webhook route from CSRF/authinternal/db/organisations.go— update plan on webhook eventssettings.html+web/static/app/pages/settings.js— billing UI buttonssupabase/migrations/— addstripe_price_id,stripe_customer_idcolumns.env.example,docs/operations/ENV_VARS.md— new env varsDockerfile— no changes needed (Go binary, no new static assets)Effort estimate
~1-2 days for core flow (checkout + webhooks + settings UI). Stripe Checkout and Billing Portal eliminate most of the frontend work.
Open questions