logicspike/docs

Billing

Billing System — Implementation Status & Resume Guide

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 webhooks

Key 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:

  1. Reads current plan's plan_service_limits
  2. Reads active tenant_addons
  3. Upserts tenant_services with merged limits

Webhook Handler

The webhook at POST /billing/webhook:

  1. Verifies HMAC-SHA256 signature using RAZORPAY_WEBHOOK_SECRET
  2. Checks idempotency via processed_payment_events
  3. Handles events: subscription.activated, subscription.cancelled, payment.captured (coin credits)

Coin System

  • Atomic operations: creditCoins uses upsert + SQL increment; debitCoins checks balance first
  • Transaction log: Every credit/debit creates a coin_transactions entry
  • 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)

  1. User clicks "Upgrade" → POST /billing/checkout → receives subscriptionId + keyId
  2. Opens window.Razorpay modal using checkout.js script
  3. After payment → POST /billing/payment/verify with signature
  4. 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 Dashboard

Frontend (.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, business

3. 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 limit

Implement 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:

  1. Queries tenant_addons where next_renewal < now() AND status = 'active'
  2. Attempts debitCoins for each
  3. If successful → update next_renewal to +30 days
  4. 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

  1. Razorpay client uses native fetch — no Node.js SDK (CF Workers compatible)
  2. Webhook signature = HMAC-SHA256X-Razorpay-Signature header verified against raw body
  3. Payment signature = HMAC-SHA256order_id|payment_id for orders, payment_id|subscription_id for subscriptions
  4. Idempotencyprocessed_payment_events table prevents double-processing webhooks
  5. Entitlements rebuild pattern — Any billing change calls rebuildEntitlements() which upserts tenant_services
  6. Soft downgrade — Never delete data; block new creation when limits exceed plan
  7. Workspace-scoped — Each tenant has its own Razorpay customer, subscription, and coin wallet
  8. 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'))"
Billing