Stripe checkout & portal

Ideafy uses Stripe for all paid billing. Checkout runs on Stripe's hosted pages; post-purchase management runs through the Stripe customer portal. You never enter a card number in Ideafy's own UI.

Upgrading from trial to paid

  1. Settings → Billing → Upgrade
  2. Ideafy calls POST /api/stripe/checkout with { teamId, plan, seats }. Seats must be ≥ current member count
  3. Backend validates you're owner or admin of the team, creates (or reuses) a Stripe customer for the team, and returns a checkout session URL
  4. You're redirected to Stripe Checkout — Stripe's hosted page, secure card input, 3D Secure if required
  5. On success, Stripe redirects you to /billing/success?session_id=<id> in the Ideafy app
  6. Stripe sends a customer.subscription.created webhook to Ideafy, which creates a row in the subscriptions table with the new subscription ID, status, period, and seat count
  7. The team's pool writes unblock within a few seconds, as the app picks up the new status

Managing the subscription

Everything post-checkout lives in the Stripe customer portal, not in Ideafy's UI. To access it:

  1. Settings → Billing → Manage subscription
  2. Ideafy calls POST /api/stripe/portal which returns a Stripe-hosted portal link
  3. Click through — you land in Stripe's portal for your team's customer

In the portal you can:

  • Update the payment method
  • Resize seats
  • Switch plans (Monthly ↔ Annual)
  • Download invoices
  • Cancel the subscription

Changes in the portal propagate back to Ideafy via webhooks. Resize seats → customer.subscription.updated → Ideafy picks up the new seat count and enforces it. Cancel → customer.subscription.deleted → Ideafy marks status canceled, starts the grace period.

Webhook handling

Ideafy handles these webhooks at /api/stripe/webhook:

EventWhat Ideafy does
customer.subscription.createdInsert a subscriptions row with status, period, seats
customer.subscription.updatedUpdate status, current_period_end, is_active (true when status ∈ active / trialing)
invoice.payment_succeededSet is_active = true, unblock pool writes
invoice.payment_failedSet status past_due, start the grace window
customer.subscription.deletedSet status canceled, grace window begins

Webhook signatures are verified. Replaying events is safe — the handler is idempotent.

Proration

Stripe prorates everything that happens mid-period:

  • Adding seats: you're charged a prorated amount immediately for the rest of the period. Next invoice is the full new amount
  • Removing seats: you get a prorated credit toward your next invoice. No refund to card
  • Switching Monthly → Annual: Stripe credits remaining Monthly time toward the Annual charge
  • Switching Annual → Monthly: Stripe credits unused Annual time toward Monthly. You'll usually prepay a few months

Ideafy doesn't surface the proration math — the Stripe portal shows you the exact amount before you confirm.

Troubleshooting checkout

  • Checkout opens blank: Stripe's fraud protection may block your IP. Try a different network
  • Payment succeeds but Ideafy still blocks the pool: the webhook may not have arrived yet. Wait 10 seconds and retry. If still blocked, check Settings → Billing → Refresh status, which forces a re-read of subscriptions from Supabase
  • "You can't reduce seats below your member count": remove members first, then reduce seats in the portal

Invoices

Invoices live in the Stripe customer portal — Billing history → download each PDF. Ideafy doesn't store copies. If you need accounting artefacts, pull them from Stripe directly.


Prev: Plans & pricing Next: Subscription states & paywall Up: User guide index

Last updated: 2026-04-13