Skip to content

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 gating

Required env vars

  • STRIPE_SECRET_KEY — server-side Stripe API key (test in dev, live in prod)
  • STRIPE_WEBHOOK_SECRET — for verifying webhook signatures
  • STRIPE_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:

EventEffect on Atlas
checkout.session.completedCreates or updates the user's subscription record; provisions plan features.
customer.subscription.updatedUpdates plan tier, status, period end.
customer.subscription.deletedMarks subscription as cancelled; user drops to free tier at period end.
invoice.payment_failedMarks subscription as past-due; surfaces a banner in the app.
invoice.payment_succeededClears 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":

  1. Check Stripe Dashboard → Customer → most recent subscription. Confirm the plan and status.
  2. Check Atlas DB → subscriptions table for that user's record. Compare to Stripe.
  3. Check webhook delivery in Stripe Dashboard → Developers → Webhooks → recent events. Look for failed deliveries.
  4. If a webhook was missed, replay it from Stripe (one-click in the dashboard). Atlas is idempotent.
  5. 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.

Released under the project license. Public sources only — no proprietary or restricted data.