logicspike/docs

Billing

Billing — Deploy Checklist

Consolidated checklist for deploying the 8-phase billing hardening pass (Phases 1–8 shipped on the billing-A-plus work). Go through top to bottom; each step is either idempotent or clearly marked as one-time.


1. Migrations (apply in order)

All hand-written, idempotent (IF NOT EXISTS). Run against the shared core Neon database via psql or the Neon SQL console:

psql "$DATABASE_URL" -f packages/core-database/drizzle/0013_credit_buckets.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0014_pending_upgrade_fields.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0015_rate_limit_buckets.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0016_plan_limits_tier_tightening.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0017_past_due_since.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0018_reconciliation_queue.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0019_billing_perf_indexes.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0020_signature_failures.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0021_integrity_reports.sql
psql "$DATABASE_URL" -f packages/core-database/drizzle/0022_billing_notifications.sql

Note: 0012_clever_titania.sql was auto-generated by drizzle-kit and is tracked in _journal.json — it runs via pnpm migrate from the core-database package.


2. Environment Variables

Set in Cloudflare Workers dashboard for logicspike-manager:

Name Required Notes
DATABASE_URL Existing
RAZORPAY_KEY_ID Existing
RAZORPAY_KEY_SECRET Existing
RAZORPAY_WEBHOOK_SECRET Existing
GATEWAY_SECRET Existing
BILLING_ALERT_WEBHOOK_URL Optional NEW — Slack Incoming Webhook URL for critical alerts. If unset, alerts fall back to structured error logs.

3. Code Deploy

cd apps/manager
pnpm install  # picks up new devDeps (vitest, pglite)
pnpm check-types
pnpm test  # should be 161 green
pnpm deploy

Also deploy packages consumers if you're bumping versions:

# only needed if core-billing or core-database changed (they did)
cd packages/core-billing && pnpm build  # if there's a build step
cd packages/core-database && pnpm build

4. Cloudflare Scheduled Workers (new crons to wire)

Add 5 scheduled triggers that POST to internal endpoints with x-gateway-key: <GATEWAY_SECRET>:

Trigger Path Frequency Purpose
reconcile POST /manager/billing/internal/reconcile */5 * * * * (every 5 min) Retry flaky Razorpay calls
escalation POST /manager/billing/internal/escalation 0 * * * * (hourly) Past-due reminders + auto-downgrade
addon_renewals POST /manager/billing/internal/addon-renewals 0 * * * * (hourly) Debit recurring addons
cleanup POST /manager/billing/internal/cleanup 0 * * * * (hourly) Prune old rate-limit + reconciliation rows
audit_integrity POST /manager/billing/internal/audit-integrity 0 3 * * * (daily 3 AM UTC) Invariant audit

Reference config (example wrangler.toml snippet for a companion logicspike-billing-crons worker that fans out to manager):

[[triggers.crons]]
schedule = "*/5 * * * *"

The cron handler itself is a short Worker that POSTs to the manager endpoints with the shared secret. No business logic lives there.


5. Alert Configuration

If you set BILLING_ALERT_WEBHOOK_URL, these events will page you:

Critical (immediate action):

  • Reconciliation task abandoned after 10 retries
  • Integrity audit found balance or ledger mismatch
  • Signature-failure spike (10/hr per tenant on /payment/verify)
  • Signature-failure spike (10/hr cumulative on /webhook)
  • Auto-downgrade executed for a tenant

Warning:

  • Integrity audit found stale pending upgrade (>72h)

Alerts write to the structured log regardless of webhook config.


6. Post-Deploy Verification

Run these queries 10 minutes after deploy to confirm health:

-- Migrations applied (should return 10 new tables)
SELECT table_name FROM information_schema.tables
WHERE table_name IN (
  'billing_reconciliation_queue',
  'billing_signature_failures',
  'billing_integrity_reports',
  'billing_notifications',
  'rate_limit_buckets'
);
 
-- Credit bucket columns present on tenant_credits
SELECT column_name FROM information_schema.columns
WHERE table_name = 'tenant_credits';
-- should include: subscription_balance, subscription_expires_at, permanent_balance
 
-- Pending-upgrade columns on subscriptions
SELECT column_name FROM information_schema.columns
WHERE table_name = 'subscriptions'
  AND column_name IN ('pending_razorpay_subscription_id',
                      'pending_billing_cycle', 'past_due_since');
 
-- First cron tick should fire within 10 min; check it happened
SELECT MAX(processed_at) FROM processed_payment_events;

Then smoke-test from the dashboard:

  1. Buy a credit pack (test mode) → GET /billing/credits/balance shows buckets.permanent incremented.
  2. Start a plan upgrade but close the modal → no state change in DB.
  3. Complete an upgrade → credits show up in buckets.subscription with an expires_at 30 days out.

7. Frontend Updates

Required for the full experience (see docs/billing/SYSTEM-REFERENCE.md sections on pending-change):

Must-ship with backend (breaks UX otherwise):

  • Widen BillingCurrent type in apps/seller-dashboard/src/hooks/use-billing.ts
  • Add "Complete payment" + "Cancel upgrade" banner driven by billing.pendingChange.kind === "in_flight_upgrade"
  • Add "Cancel scheduled downgrade" banner driven by billing.pendingChange.kind === "scheduled_downgrade"
  • Wire POST /billing/pending-change/cancel
  • Handle 429 responses globally with Retry-After

Should-ship within a week:

  • Stage-based past-due alert styling (alerts[].stage)
  • Credit bucket tooltip showing "X expiring on {date} + Y permanent"

When admin UI exists:

  • POST /billing/admin/adjust-credits form
  • GET /billing/admin/signature-failures dashboard

8. Rollback Plan

Each migration is additive (new columns, new tables) — no drops, no destructive UPDATEs. Rollback is code-only:

  1. Revert the manager deploy to the prior Worker version in the Cloudflare dashboard.
  2. The new tables + columns stay — they're NULL/DEFAULT on existing rows, don't affect old code.
  3. Disable the 5 new Scheduled Worker triggers.

No data loss. You can redeploy forward after fixing any issue.


9. Known Edge Cases

  • Yearly plans dispense 12× baseCredits upfront (Phase 1.3). If your pricing page says "180k credits/year for Pro", yearly users now correctly get 180k at each annual renewal (not 15k).
  • Legacy tenant_credits.balance column is maintained by every write path as a mirror of effective_sub + permanent. Deprecated but still populated. Readers should prefer the bucket columns.
  • /internal/credits/debit is deprecated — no service currently uses it. Services go through CreditLedger in @repo/core-billing.
  • planFeatures table is superseded by planServiceLimits. Kept to avoid schema break; will be dropped in a future migration.

10. Testing Target

After deploy, expected test counts:

Package Tests
apps/manager 161
packages/core-billing 40
Total 201

Run locally pre-deploy:

cd apps/manager && pnpm test
cd packages/core-billing && pnpm test
Billing