Last Updated: 2026-02-28
Status: ✅ Backend Complete · ✅ Frontend UI Complete · ⏸️ Paused (Free plan only)
Resume ETA: ~1 month (after blog maturation)
📌 Quick Summary
The billing system is fully implemented but currently only serves the Free plan. Paid plans, coin purchases, and add-ons are built and ready — they just need Razorpay live keys and production seeding to go live.
What's Working Now
- Every new workspace gets auto-provisioned with a Free plan + 0 coin balance
- Billing settings page renders at
/dashboard/settings/billing(5 tabs) - All 19 API endpoints are deployed and type-checked
What Needs Activation Later
- Configure Razorpay live keys (currently using test keys)
- Seed Razorpay Plans (plan IDs must be created in Razorpay Dashboard and mapped to DB)
- Enable limit enforcement across services (Phase 7)
- Build recurring add-on renewal worker (Phase 5 from plan)
🗂️ File Map
Backend (Manager Service)
| File | Purpose | Lines |
|---|---|---|
routes/billing.ts |
All 19 billing API route handlers | ~868 |
services/billing.ts |
Core billing logic: getPublicPlans, getBillingCurrent, provisionFreePlan, rebuildEntitlements | ~204 |
services/coins.ts |
Coin operations: balance, credit, debit, packs, addons | ~215 |
providers/razorpay.provider.ts |
Razorpay API wrapper (CF Workers compatible) | ~160 |
utils/razorpay-client.ts |
Low-level HTTP client + HMAC-SHA256 verification | ~106 |
Frontend (Seller Dashboard)
| File | Purpose |
|---|---|
billing/page.tsx |
Full billing settings page — 5 tabs with Razorpay checkout |
Gateway
| File | What was changed |
|---|---|
manager.proxy.ts |
Added /billing/webhook and /billing/plans to publicPaths |
gateway.middleware.ts |
Added GATEWAY_BYPASS_PATHS for webhook + plans |
Shared Packages
| Package | File | What was added |
|---|---|---|
@repo/core-billing |
constants.ts |
PLAN_IDS, LIMIT_KEYS, BILLING_EVENTS |
@repo/core-types |
billing.ts |
TypeScript types: Plan, Subscription, BillingAlert, CoinTransaction, etc. |
@repo/core-database |
schema/billing.ts |
All billing DB tables (see Schema section below) |
Docs
| File | Content |
|---|---|
docs/billing/architecture.md |
Full architecture diagram + design decisions |
docs/billing/implementation_plan.md |
Phase-by-phase task breakdown |
docs/billing/api_specification.md |
API endpoint documentation |
docs/billing/razorpay_setup_guide.md |
How to configure Razorpay Dashboard |
docs/billing/user_journeys.md |
User flows (checkout, upgrade, downgrade, etc.) |
docs/billing/BILLING_STATUS.md |
This file — resume guide |
Seed Data
| File | Content |
|---|---|
seed.sql |
Plans (free/starter/pro/business), services, plan_service_limits, coin_packs |
🔌 All 19 API Endpoints
Phase 1 — Foundation (2 endpoints)
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/billing/plans |
Public | List all public plans with limits (pricing page) |
GET |
/billing/current |
Tenant | Current plan, subscription status, coin balance, alerts |
Phase 2 — Checkout (3 endpoints)
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/billing/checkout |
Owner | Create Razorpay subscription → returns modal data |
POST |
/billing/payment/verify |
Tenant | Verify HMAC signature + activate subscription |
POST |
/billing/webhook |
Razorpay (HMAC) | Process Razorpay events (payment, subscription lifecycle) |
Phase 3 — Subscription Management (6 endpoints)
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/billing/cancel |
Owner | Cancel subscription at period end |
POST |
/billing/change-plan |
Owner | Upgrade/downgrade plan |
POST |
/billing/switch-cycle |
Owner | Switch monthly ↔ yearly |
GET |
/billing/invoices |
Tenant | Payment history from Razorpay |
GET |
/billing/info |
Tenant | Billing details (company, GST) |
PUT |
/billing/info |
Owner | Update billing details |
Phase 4 — Coins & Add-ons (8 endpoints)
| Method | Path | Auth | Purpose |
|---|---|---|---|
GET |
/billing/coins/balance |
Tenant | Current coin balance |
GET |
/billing/coins/transactions |
Tenant | Paginated transaction history |
GET |
/billing/coins/packs |
Public | Available coin packs |
POST |
/billing/coins/buy |
Owner | Create Razorpay order for coin pack |
POST |
/billing/coins/verify |
Tenant | Verify payment + credit coins |
GET |
/billing/addons |
Tenant | Add-on catalog + tenant's active add-ons |
POST |
/billing/addons/buy |
Owner | Deduct coins → create addon → rebuild entitlements |
POST |
/billing/addons/cancel |
Owner | Pause addon → rebuild entitlements |
🗄️ Database Schema
All tables live in @repo/core-database/src/schema/billing.ts:
plans — Free, Starter, Pro, Business definitions
services — Service catalog (blog, media, platform)
plan_service_limits — Limits per plan × service × key
subscriptions — One per tenant, links to plan + Razorpay
tenant_services — Effective limits (rebuilt on plan/addon change)
tenant_coins — Coin wallet (one per tenant)
coin_transactions — Credit/debit ledger
coin_packs — Purchasable coin bundles
addon_catalog — Available add-ons
tenant_addons — Purchased add-ons per tenant
processed_payment_events — Idempotency tracking for webhooksKey Relationships
tenant ──→ subscription ──→ plan ──→ plan_service_limits
tenant ──→ tenant_services (effective limits, rebuilt by rebuildEntitlements)
tenant ──→ tenant_coins ──→ coin_transactions
tenant ──→ tenant_addons ──→ addon_catalog🏗️ Core Business Logic
provisionFreePlan(db, tenantId)
Called during workspace creation. Creates:
- Subscription record (plan:
free, status:active) - Coin wallet (balance: 0)
rebuildEntitlements(db, tenantId)
Called after any billing change (plan upgrade/downgrade, addon buy/cancel). It:
- Reads current plan's
plan_service_limits - Reads active
tenant_addons - Upserts
tenant_serviceswith merged limits
Webhook Handler
The webhook at POST /billing/webhook:
- Verifies HMAC-SHA256 signature using
RAZORPAY_WEBHOOK_SECRET - Checks idempotency via
processed_payment_events - Handles events:
subscription.activated,subscription.cancelled,payment.captured(coin credits)
Coin System
- Atomic operations:
creditCoinsuses upsert + SQL increment;debitCoinschecks balance first - Transaction log: Every credit/debit creates a
coin_transactionsentry - Add-on purchase:
buyAddon→ debitCoins → insert addon → rebuildEntitlements
🖥️ Frontend — Billing Settings Page
Location: /dashboard/settings/billing
Component: seller-dashboard/src/app/dashboard/settings/billing/page.tsx
5 Tabs
| Tab | What's Rendered | API Calls |
|---|---|---|
| Overview | Plan badge + status, trial/past-due/cancel banners, coin balance, Cancel button | GET /billing/current |
| Plans | 4 pricing cards, monthly/yearly toggle, upgrade/downgrade/trial CTAs | GET /billing/plans, POST /billing/checkout, POST /billing/change-plan |
| Invoices | Table with Date, Amount, Tax, Status, PDF download link | GET /billing/invoices |
| Coins & Add-ons | Coin balance, buy coin packs (Razorpay), addon marketplace, transaction history | GET /coins/packs, POST /coins/buy, POST /coins/verify, GET /addons, POST /addons/buy, POST /addons/cancel |
| Billing Info | Company Name + GST fields → Save button | GET /billing/info, PUT /billing/info |
Razorpay Checkout Flow (Frontend)
- User clicks "Upgrade" →
POST /billing/checkout→ receivessubscriptionId+keyId - Opens
window.Razorpaymodal using checkout.js script - After payment →
POST /billing/payment/verifywith signature - On success → refetch billing data → UI updates
🔐 Environment Variables
Manager Service (.dev.vars / Cloudflare Secrets)
RAZORPAY_KEY_ID=rzp_test_xxxxxxxxxxxxx # Test key (switch to live for prod)
RAZORPAY_KEY_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx # Test secret
RAZORPAY_WEBHOOK_SECRET=<generated-strong-string> # Must match Razorpay DashboardFrontend (.env.local)
NEXT_PUBLIC_GATEWAY_URL=http://127.0.0.1:8788 # Points to Cloudflare Workers gateway✅ What's Done (Phases 1–5)
- Phase 1 — Database schema, plan seeding,
GET /plans,GET /current, free plan provisioning - Phase 2 — Razorpay checkout flow, payment verification, webhook handler
- Phase 3 — Cancel, change-plan, switch-cycle, invoices, billing info endpoints
- Phase 4 — Coin service, coin purchase/verify, add-on buy/cancel, 8 new endpoints
- Phase 5 — Full billing settings UI (5-tab page with Razorpay modal integration)
🔮 What's Left To Do (When You Resume)
Immediate (Before Going Live with Paid Plans)
1. Configure Razorpay Live Keys
1. Login to Razorpay Dashboard → Settings → API Keys → Generate Live Key
2. Create Plans in Razorpay Dashboard:
- Starter Monthly (₹499/mo)
- Starter Yearly (₹4,999/yr)
- Pro Monthly (₹1,999/mo)
- Pro Yearly (₹19,999/yr)
- Business Monthly (₹4,999/mo)
- Business Yearly (₹49,999/yr)
3. Copy Plan IDs (plan_...) and update seed.sql:
- plans.razorpay_plan_id_monthly
- plans.razorpay_plan_id_yearly
4. Set Webhook URL in Razorpay Dashboard:
URL: https://logicspike-gateway.starkdipanshu456.workers.dev/manager/billing/webhook
Events: subscription.activated, subscription.cancelled, subscription.updated, payment.captured
5. Update production secrets:
- RAZORPAY_KEY_ID (live key)
- RAZORPAY_KEY_SECRET (live secret)
- RAZORPAY_WEBHOOK_SECRET (must match dashboard)2. Seed Razorpay Plans to Database
Update seed.sql with the live Razorpay Plan IDs, then re-run seeding:
UPDATE plans SET razorpay_plan_id_monthly = 'plan_LIVE_xxx' WHERE id = 'starter';
UPDATE plans SET razorpay_plan_id_yearly = 'plan_LIVE_yyy' WHERE id = 'starter';
-- repeat for pro, business3. Seed Coin Packs & Addon Catalog
Add rows to coin_packs and addon_catalog tables. Example:
INSERT INTO coin_packs (id, name, coins, bonus_pct, price_paise, sort_order, is_active)
VALUES
('pack_100', '100 Coins', 100, 0, 9900, 1, true),
('pack_500', '500 Coins', 500, 10, 44900, 2, true),
('pack_1000', '1000 Coins', 1000, 20, 79900, 3, true);
INSERT INTO addon_catalog (id, display_name, description, service_code, limit_key, units_per_purchase, coin_cost_per_unit, is_recurring, is_active)
VALUES
('addon_extra_posts', 'Extra Blog Posts', '+50 blog posts', 'blog', 'posts', 50, 100, false, true),
('addon_extra_storage', 'Extra Storage', '+5 GB storage', 'media', 'storage_mb', 5120, 200, true, true),
('addon_extra_seats', 'Extra Team Seats', '+5 team members', 'platform', 'seats', 5, 150, true, true);Phase 7 — Limit Enforcement (Not Yet Built)
Every resource-creating endpoint should check limits before allowing creation:
- POST /posts → check blog.posts limit
- POST /upload → check media.storage_mb limit
- POST /invitations → check platform.seats limit
- POST /api-keys → check platform.api_keys limit
- POST /custom-roles → check platform.custom_roles limitImplement checkLimit(tenantId, serviceCode, limitKey) in @repo/core-billing/src/limit-guard.ts.
Phase 8 — Stripe Provider (International)
The RazorpayProvider pattern is designed for a second provider:
// providers/stripe.provider.ts
export class StripeProvider implements IPaymentProvider { ... }Select provider based on tenant's country/billing region.
Phase 9 — Recurring Add-on Renewal Worker
Build a Cloudflare Cron Trigger that:
- Queries
tenant_addonswherenext_renewal < now()ANDstatus = 'active' - Attempts
debitCoinsfor each - If successful → update
next_renewalto +30 days - If insufficient → pause addon, notify user
🧪 Testing Checklist (When Resuming)
□ Checkout flow: Free → Starter (test card 4111 1111 1111 1111)
□ Payment verify: Signature validation passes
□ Webhook: payment.captured event credits coins
□ Upgrade: Starter → Pro (via change-plan)
□ Downgrade: Pro → Starter (scheduled at cycle end)
□ Cancel: Subscription cancels at period end
□ Coin purchase: Buy pack → verify → balance increases
□ Addon buy: Deducts coins, creates addon, entitlements rebuild
□ Addon cancel: Pauses addon, entitlements rebuild
□ Invoice list: Shows payment history from Razorpay
□ Billing info: Save + load company name and GST
□ Access control: Non-owners see limited tabs
□ Free plan auto-provision: New workspace gets free plan🧠 Key Design Decisions to Remember
- Razorpay client uses native
fetch— no Node.js SDK (CF Workers compatible) - Webhook signature = HMAC-SHA256 —
X-Razorpay-Signatureheader verified against raw body - Payment signature = HMAC-SHA256 —
order_id|payment_idfor orders,payment_id|subscription_idfor subscriptions - Idempotency —
processed_payment_eventstable prevents double-processing webhooks - Entitlements rebuild pattern — Any billing change calls
rebuildEntitlements()which upsertstenant_services - Soft downgrade — Never delete data; block new creation when limits exceed plan
- Workspace-scoped — Each tenant has its own Razorpay customer, subscription, and coin wallet
- Gateway bypass — Webhook and plans endpoints skip auth middleware (verified by HMAC instead)
📞 Quick Reference Commands
# Type-check billing code
npx tsc --noEmit --project apps/manager/tsconfig.json
# Run dev
turbo run dev
# Re-seed database
psql $DATABASE_URL < apps/manager/seed.sql
# Generate Razorpay webhook secret
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"