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
- Settings → Billing → Upgrade
- Ideafy calls
POST /api/stripe/checkoutwith{ teamId, plan, seats }. Seats must be ≥ current member count - 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
- You're redirected to Stripe Checkout — Stripe's hosted page, secure card input, 3D Secure if required
- On success, Stripe redirects you to
/billing/success?session_id=<id>in the Ideafy app - Stripe sends a
customer.subscription.createdwebhook to Ideafy, which creates a row in thesubscriptionstable with the new subscription ID, status, period, and seat count - 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:
- Settings → Billing → Manage subscription
- Ideafy calls
POST /api/stripe/portalwhich returns a Stripe-hosted portal link - 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:
| Event | What Ideafy does |
|---|---|
customer.subscription.created | Insert a subscriptions row with status, period, seats |
customer.subscription.updated | Update status, current_period_end, is_active (true when status ∈ active / trialing) |
invoice.payment_succeeded | Set is_active = true, unblock pool writes |
invoice.payment_failed | Set status past_due, start the grace window |
customer.subscription.deleted | Set 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
subscriptionsfrom 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