Appearance
Stripe billing
Atlas brokers all payment handling to Stripe. Atlas itself never sees card numbers, never stores PCI-relevant data, and treats Stripe as the source of truth for plan state. Implemented across backend/routes/subscriptions.py and backend/routes/stripe_webhook.py.
Architecture in one paragraph
When a user clicks Manage in Settings → Subscription, Atlas redirects them to a hosted Stripe customer portal. The user changes plan, payment method, or cancels — entirely on Stripe. Stripe fires a webhook back to Atlas; the webhook handler updates the local subscription record. The local record drives feature gating in the frontend via FeatureGate.jsx.
User → Atlas Settings → Stripe Customer Portal → (Stripe events)
↓
Atlas webhook handler
↓
Local subscription store
↓
Frontend feature gatingRequired env vars
STRIPE_SECRET_KEY— server-side Stripe API key (test in dev, live in prod)STRIPE_WEBHOOK_SECRET— for verifying webhook signaturesSTRIPE_PRICE_ID_*— one per plan tier (free, pro, advisory)
The publishable key (if used for client-side Stripe.js for any flow) goes into a VITE_* env var; everything else is server-side only.
Webhook events handled
The handler at backend/routes/stripe_webhook.py processes:
| Event | Effect on Atlas |
|---|---|
checkout.session.completed | Creates or updates the user's subscription record; provisions plan features. |
customer.subscription.updated | Updates plan tier, status, period end. |
customer.subscription.deleted | Marks subscription as cancelled; user drops to free tier at period end. |
invoice.payment_failed | Marks subscription as past-due; surfaces a banner in the app. |
invoice.payment_succeeded | Clears past-due status; logs the invoice for the user's records. |
All events are signature-verified against STRIPE_WEBHOOK_SECRET — unverified payloads are rejected.
Plan state and feature gating
The local subscription record stores plan tier, status (active, past_due, canceled), period end, and the cached feature-flag bitmap. The frontend reads this via the user-info endpoint on every login plus on a 5-minute refresh interval. Stale data degrades gracefully — feature gates default to "deny" rather than "allow" if the record can't be loaded.
Debugging a desync
If a user reports "I upgraded but I still see free-tier limits":
- Check Stripe Dashboard → Customer → most recent subscription. Confirm the plan and status.
- Check Atlas DB →
subscriptionstable for that user's record. Compare to Stripe. - Check webhook delivery in Stripe Dashboard → Developers → Webhooks → recent events. Look for failed deliveries.
- If a webhook was missed, replay it from Stripe (one-click in the dashboard). Atlas is idempotent.
- If state is right in Atlas but the frontend still misbehaves, the user's session may be cached — sign out and back in, or clear local storage.
What Atlas does NOT do
- Store card numbers, CVCs, or any PCI-relevant data.
- Run a payment-method UI inside Atlas — all card entry is on Stripe-hosted pages.
- Issue refunds programmatically — refunds are manual in the Stripe dashboard.