Stripe activation
The Tanvrit Compute billing pipeline is code-complete. Checkout sessions, subscription tracking, cancel-at-period-end, and webhook reconciliation are all implemented on the server (see com.tanvrit.server.feature.compute.service.ComputeBillingServiceImpl and .route.StripeWebhookRouter) and wired through the portal's BillingScreen. Only the operational setup is pending.
One-time activation
1. Register the Stripe account
<https://dashboard.stripe.com/register> — use Test mode throughout activation; switch to Live mode once the full flow works end-to-end.
2. Create prices
Each of the three paid tiers needs a recurring monthly price:
| Plan ID (in our DB) | Tier | Monthly | |---|---|---| | plan-starter | STARTER | $15 | | plan-pro | PRO | $49 | | plan-enterprise | ENTERPRISE | custom — use a metered price or contact-sales |
In the Stripe dashboard → Products → Add product → set recurring / monthly / the amount above. Copy the generated price_id (price_XXXXXXXXXXXXXXXX).
3. Populate price IDs in the compute-server database
seedDefaultPlans() creates the plan rows with blank stripePriceId. After creating prices in Stripe, update the rows:
// In a mongosh session against the prod DB:
db.compute_billing_plans.updateOne(
{ planId: "plan-starter" },
{ $set: { stripePriceId: "price_XXXXXXXXXXXXXXXX" } }
);
// repeat for plan-pro and plan-enterprise (if Enterprise is self-serve).
Without this, the server rejects checkout with Plan plan-XXX has no Stripe price ID configured.
4. Set server env vars
Production deploy must have both of these set:
STRIPE_SECRET_KEY=sk_live_… # or sk_test_… during activation
STRIPE_WEBHOOK_SECRET=whsec_… # from the webhook endpoint setup below
With neither set: checkout.session.completed returns Stripe not configured — set STRIPE_SECRET_KEY. Free tier still works.
With STRIPE_SECRET_KEY but no STRIPE_WEBHOOK_SECRET: checkout works but incoming webhooks are accepted without signature verification (a warn line is logged; dev-mode-only fallback).
5. Register the webhook endpoint
Stripe dashboard → Developers → Webhooks → Add endpoint.
- Endpoint URL:
https://api.tanvrit.com/api/compute/stripe/webhook - Events to send:
- checkout.session.completed — upserts the local subscription after the user pays for the first time. - customer.subscription.updated — status and period-end changes. - customer.subscription.deleted — user fully cancelled. - invoice.payment_failed — currently just logged; wire an alert when the subscription-dunning flow lands.
Click Reveal signing secret and paste it into STRIPE_WEBHOOK_SECRET.
6. Smoke-test in Test mode
- Deploy with
STRIPE_SECRET_KEY=sk_test_…and the test-mode webhook - In the portal → Billing, click Subscribe on Starter.
- Stripe opens checkout with the 4242-4242-4242-4242 test card;
- Verify the portal's Current plan updates to Starter within ~2s
- In the Stripe dashboard → Customers, cancel the subscription and
secret.
complete it.
(webhook-driven).
verify our DB updates the status and cancelAtPeriodEnd.
When this works, flip STRIPE_SECRET_KEY to sk_live_… and register the prod webhook endpoint. Only change in the code: nothing — same code path, different Stripe mode.
Free tier
Users on the Free plan never hit Stripe. plan-free has monthlyPriceUsdCents = 0 and stripePriceId = "" — the server skips checkout entirely and drops them straight into the 30,000-credit quota. No activation action needed.
Developer testing without Stripe
Leave STRIPE_SECRET_KEY unset. The /api/compute/billing/checkout endpoint returns "Stripe not configured — set STRIPE_SECRET_KEY" as its status message, which the portal surfaces verbatim. Free tier still works. Use this mode for local dev and CI.
Architecture (for future maintainers)
portal BillingScreen ──▶ /api/compute/billing/checkout
│
▼
ComputeBillingService ──▶ Stripe API (Session.create)
│
▼
checkoutUrl returned
│
┌───────────────────┘
▼
portal opens checkout URL (LocalUriHandler)
│
▼
user pays on Stripe's hosted page
│
▼
Stripe → /api/compute/stripe/webhook
│
▼
ComputeBillingServiceImpl.upsertSubscriptionFromWebhook
(stores Stripe customer id, subscription id, status)
│
▼
portal next Billing refresh shows ACTIVE subscription
No polling, no server-side redirect handling — the webhook is the sole source of truth for subscription state.