Tanvrit Compute

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

  1. Deploy with STRIPE_SECRET_KEY=sk_test_… and the test-mode webhook
  2. secret.

  3. In the portal → Billing, click Subscribe on Starter.
  4. Stripe opens checkout with the 4242-4242-4242-4242 test card;
  5. complete it.

  6. Verify the portal's Current plan updates to Starter within ~2s
  7. (webhook-driven).

  8. In the Stripe dashboard → Customers, cancel the subscription and
  9. 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.