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

163 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.ts``Customer` 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.ts``PUT /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
- [x] Extend `tests/db-customer.test.ts`: migration on existing DB adds columns with `'stripe'` default; round-trip `billingType`/`email`
- [x] Extend `tests/admin.test.ts`: `PUT /customers/:id` sets `billingType: 'purchase_order'`; invalid value → 400
- [x] 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:**
```ts
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'
```
- [x] Tests: line grouping, credit math (partial/zero/excess), id format, PO fields
- [x] Repo tests: save/get/list filters, sequence increments per period
- [x] Implement; commit `feat(accounting): invoice engine`
### Task 3: Reports service (billing + trends, CSV/JSON)
**Files:**
- Create: `src/reports.ts``billingRows(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`
- [x] 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
- [x] 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`
- [x] Tests per route incl. CSV content-type, filter combos, invoice lifecycle transitions (draft→issued→paid; illegal transitions → 409)
- [x] 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 13: `docs/ACCOUNTING.md` + PDF — company accounting walkthrough (invoices, PO billing, reports, trends, CSV export) with screenshots
### 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.