zappier-edge/docs/superpowers/plans/2026-07-27-accounting-portal.md

10 KiB
Raw Blame History

Accounting, User Management & Customer Portal — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [x]) syntax for tracking.

Goal: Extend Zappier with three surfaces: (a) company accounting & system management, (b) internal user management, and (c) an end-user customer portal — with full documentation and screenshot walkthroughs for each.

Architecture: Same ports-and-adapters style as the existing codebase. New domain modules (src/invoicing.ts, src/reports.ts, src/accounts.ts) expose pure logic + repo interfaces; SQLite adapters live in src/db/; HTTP wiring goes into src/admin.ts (company side) and a new src/portal.ts (customer side). The admin SPA gains tabs; the portal is a second dependency-free SPA under portal/. No new runtime deps in Phase 12; Phase 3 adds otplib-style TOTP only if unavoidable — prefer node:crypto scrypt for passwords and a minimal TOTP implementation per RFC 6238.

Tech Stack: Node 20 · TS strict · Express 4 · better-sqlite3 · jest/supertest.

Global Constraints

  • Money is integer cents everywhere; multipliers applied once, Math.round.
  • Money/date logic must be pure functions, unit-tested without HTTP or SQLite.
  • All SQL in src/db/ repos only; all tables CREATE TABLE IF NOT EXISTS; schema changes for existing tables use idempotent ALTER TABLE ... ADD COLUMN guarded by PRAGMA table_info checks.
  • Existing 87 jest tests + 4 mocha tests stay green; npx tsc --noEmit clean after every task.
  • No servers left running after any verification; walkthrough screenshots use a throwaway ZAPPIER_DB.
  • PDFs: Phase 12 deliver print-ready HTML report/invoice pages (browser Print → PDF, zero deps) plus CSV; binary PDF generation is deferred unless the owner asks.
  • The demo password $$$Adm1n### and admin-dev-key remain dev defaults, overridable by env.

Surface map

Surface URL Auth Audience
Admin SPA (extended) /admin login token / x-admin-key Company ops & accounting
Customer portal SPA (new) /portal customer session token (+TOTP 2FA) End-user customers
Public API /v1 x-api-key Unchanged

Phase 1 — Accounting backend

Task 1: Schema + customer billing identity

Files:

  • Modify: src/auth.tsCustomer gains billingType: 'stripe' | 'purchase_order' (default 'stripe'), email?: string
  • Modify: src/db/customer-repo.ts — idempotent ALTER TABLE customers ADD COLUMN billing_type TEXT NOT NULL DEFAULT 'stripe'; same for email TEXT; map in toCustomer
  • Modify: src/admin.tsPUT /customers/:id accepts billingType (validated) and email
  • Test: tests/db-customer.test.ts, tests/admin.test.ts (extend)

Interfaces:

  • Consumes: existing CustomerRepo

  • Produces: Customer.billingType, Customer.email used by all later tasks

  • Extend tests/db-customer.test.ts: migration on existing DB adds columns with 'stripe' default; round-trip billingType/email

  • Extend tests/admin.test.ts: PUT /customers/:id sets billingType: 'purchase_order'; invalid value → 400

  • Implement; run suites; commit feat(accounting): customer billing types and email

Task 2: Invoice engine

Files:

  • Create: src/invoicing.ts — types + pure generation logic + repo interface
  • Create: src/db/invoice-repo.ts — SQLite adapter
  • Test: tests/invoicing.test.ts, tests/db-invoice.test.ts

Interfaces:

export interface Invoice {
  id: string;                 // INV-2026-07-<customerSeq>
  customerId: string;
  period: string;             // YYYY-MM
  status: 'draft' | 'issued' | 'paid';
  lines: InvoiceLine[];       // { endpointId, calls, cents }
  totalCents: number;         // sum of lines (gross usage)
  creditCents: number;        // monthly credit applied
  billableCents: number;      // totalCents - creditCents, floored at 0
  billingType: 'stripe' | 'purchase_order';
  poNumber?: string;          // PO billing only
  issuedAtMs?: number; dueAtMs?: number; paidAtMs?: number;
}
export interface InvoiceRepo {
  save(invoice: Invoice): void;
  get(id: string): Invoice | undefined;
  list(filter: { customerId?: string; period?: string; status?: Invoice['status'] }): Invoice[];
  nextSequence(period: string): number;
}
export function buildInvoice(args: {
  customer: Customer; period: string; sequence: number;
  entries: UsageEntry[]; tier: TierConfig; poNumber?: string;
}): Invoice;   // groups entries by endpoint; status 'draft'
  • Tests: line grouping, credit math (partial/zero/excess), id format, PO fields
  • Repo tests: save/get/list filters, sequence increments per period
  • Implement; commit feat(accounting): invoice engine

Files:

  • Create: src/reports.tsbillingRows(entries, customers, tiers, range) → rows {customerId, name, billingType, calls, totalCents, creditCents, billableCents}; usageTrend(entries, bucket: 'day'|'week')[{bucket, calls, cents}]; toCsv(rows) with RFC-4180 escaping

  • Test: tests/reports.test.ts

  • Tests: date-range filtering (inclusive from, exclusive to), per-customer vs all, per-billingType filter, trend bucketing across month boundary, CSV quoting of commas/quotes/newlines

  • Implement; commit feat(accounting): reports service

Task 4: Admin accounting API

Files:

  • Modify: src/admin.ts — new routes (all behind existing admin auth):

    • POST /invoices/generate { period, customerId?, poNumber? } → builds draft invoices for the period (all customers or one; idempotent per customer+period — regenerating replaces the draft)
    • POST /invoices/:id/issue, POST /invoices/:id/paid
    • GET /invoices?customerId&period&status
    • GET /invoices/:id (+ ?format=html print-ready invoice page)
    • GET /reports/billing?from&to&customerId&billingType&format=json|csv
    • GET /reports/usage-trend?from&to&bucket&customerId
    • GET /zapier/status{ published: boolean, triggerCount, actionCount, baseUrl } read from zapier-app/ files (static inspection, no network)
  • Test: tests/admin-accounting.test.ts

  • Tests per route incl. CSV content-type, filter combos, invoice lifecycle transitions (draft→issued→paid; illegal transitions → 409)

  • Implement; commit feat(accounting): admin accounting API


Phase 2 — Admin UI: Accounting, Reports, Users tabs

Task 5: Invoices tab (generate, filter, lifecycle, print page)

Task 6: Reports tab (date-range pickers, customer + billing-type filters, CSV download, trend charts as inline SVG)

Task 7: Users tab (admin account management: list/create/deactivate admin users backed by a new admin_users table replacing the static two-account map; login endpoint reads the table; env seed preserved)

Task 8: System tab (Zapier connection status, billing job last-run info from billing_reports/job_locks)

Each: admin UI section + tests/ coverage for any new API + screenshot verification. Commit per task.


Phase 3 — Customer portal (/portal)

Task 9: Customer identity

  • customers += password_hash, totp_secret, totp_enabled, email_verified
  • src/accounts.ts: scrypt hash/verify (node:crypto), session tokens (new portal_sessions table), signup POST /portal/api/signup (creates customer on free tier + issues API key), login POST /portal/api/login
  • Tests: hash round-trip, signup/login flows, session expiry

Task 10: TOTP 2FA

  • RFC 6238 TOTP (HMAC-SHA1, 30 s step, 6 digits) implemented in src/accounts.ts (no dep): generateTotpSecret, totpUri(secret, email), verifyTotp(secret, code, window=1)
  • Routes: POST /portal/api/2fa/setup (returns secret + otpauth URI; QR rendered client-side via a tiny inline QR lib or Google-Charts-free canvas QR — decision: render otpauth URI as text + QR via qrcode npm dep, portal-side only), POST /portal/api/2fa/enable, POST /portal/api/2fa/verify (login second step), POST /portal/api/2fa/disable
  • Tests: known RFC vectors, window tolerance, login requires second factor when enabled

Task 11: Portal dashboard API

  • GET /portal/api/me (profile, tier, apiKey, regenerate key POST /portal/api/api-key)
  • GET /portal/api/usage (month-to-date + credit)
  • GET /portal/api/invoices (own invoices only, scoped by session customer)
  • POST /portal/api/reload { amountCents } — prepaid balance: customers += balance_cents; Stripe PaymentIntent via existing SDK (test mode); balance drawn down at invoice issue before metered reporting
  • PUT /portal/api/email-invoicing { enabled, email } — stored prefs; billing job emails PO invoices (send via SMTP env config; dev: log-only transport)
  • Tests: session scoping (cannot read other customers' invoices), reload math, key regeneration invalidates old key

Task 12: Portal SPA

  • portal/index.html + portal/app.js in the same design language as the new admin: login/signup/2FA screens, dashboard (usage + balance), invoices (history + print), billing (reload, email invoicing), API docs + pricing info pages (rendered from openapi.yaml + live rate card)
  • Screenshot verification of every screen

Phase 4 — Documentation

Task 14: docs/USER-MANAGEMENT.md + PDF — admin users walkthrough

Task 15: docs/CUSTOMER-PORTAL.md + PDF — end-user walkthrough (signup, 2FA, reload, invoices, API docs)

Task 16: Refresh USER-MANUAL.md, DEVELOPER.md, README.md; re-export all PDFs


Self-review notes

  • Spec coverage: Stripe and PO billing (Tasks 12, 11), invoicing histories (2, 4, 5), CSV/PDF + date range + all/individual/by-type (3, 4, 6), usage/billing trends (3, 6), management reports (4, 6, 8), Zapier connections (4, 8), user management (7), customer login/setup/2FA/invoices/reload/email invoicing/API docs/pricing (912), docs for all three surfaces (1316).
  • Type consistency: Invoice, InvoiceRepo, billingRows, usageTrend, toCsv signatures are defined once above and reused in every later task.