# Performance West — Build Plan

**Last updated:** 2026-04-05  
**Status:** Pre-launch. CRTC pipeline ~95% built (14 steps, BITS/CCTS/compliance calendar implemented). DocServer provisioned. US formation plumbing exists but no state has been filed end-to-end. Payment flow is wired but gateways not yet configured in ERPNext UI.

> **See also:** [`docs/multi-province-plan.md`](multi-province-plan.md) — Plan to extend CRTC to multi-province + standalone Canadian formation + universal compliance calendar. 28-36 hours, 16 sub-phases.
> **See also:** [`docs/competitive-pricing.md`](competitive-pricing.md) — Competitor pricing analysis: US formation, Canadian formation, registered agents. Updated 2026-04-05.

---

## How to Read This Plan

Tracks are independent workstreams. Within each track, items are ordered by dependency — complete them top-to-bottom. Across tracks, work in parallel where possible.

**Labels:**
- `[BLOCKER]` — nothing ships until this is done
- `[CRTC]` — affects the $3,899 flagship product
- `[FORMATION]` — affects US LLC/Corp product
- `[INFRA]` — server/ops
- `[PLUMBING]` — backend wiring, no user-facing change

---

## Track 1 — CRTC Pipeline (Flagship Revenue)

The Canada CRTC Carrier Package is the highest-value product. Get one real order through end-to-end before doing anything else.

### 1.1 ERPNext Payment Config `[BLOCKER][CRTC]`
ERPNext gateways exist as code but are not configured in the UI — no payment can complete.

- [ ] Configure Crypto Payment Settings: URL = `https://pay.performancewest.net`, SHKeeper API key
- [ ] Create Payment Gateway Account `Crypto-Crypto` in ERPNext
- [ ] Configure Adyen Settings (5 instances: Card, ACH, Klarna, CashApp, AmazonPay) — pending Adyen account approval
- [ ] Create 5 Adyen Payment Gateway Accounts
- [ ] Configure ERPNext SMTP for outbound email (invoices, notifications)
- [ ] Test: place a $1 test order, confirm Payment Request is created, confirm redirect to checkout, confirm webhook fires back to Express API

### 1.2 COLIN Selector Verification `[BLOCKER][CRTC]`
The BC incorporation adapter (`frappe_ca_registry/provinces/bc/adapter.py`) has 13 steps mapped but selectors are unverified against the live portal. No real BC incorporation can complete until this is tested.

- [ ] Place a real test incorporation order against COLIN (corporateonline.gov.bc.ca)
- [ ] Verify/fix selectors for: Step 6 Director, Step 7 Office Addresses, Step 8 Share Structure, Step 9 Notification, Step 12 Payment
- [ ] Verify payment step: COLIN accepts Visa/MC — use `relay-filing-card` via `get_filing_card` API
- [ ] Implement multiple-directors loop (currently only first director is inserted)
- [ ] Confirm that BC sends certificate email to `filings@performancewest.net`
- [ ] End-to-end smoke test: `frappe_ca_registry` creates Filing Request → COLIN automation completes → certificate arrives

### 1.3 CRTC Pipeline Remaining Stubs `[CRTC]`

- [ ] **Anytime Mailbox automation hardening** — provider has no API, but Playwright flow now exists. Validate selectors against live UI and stabilize OTP retrieval via Carbonio IMAP, then keep admin handoff as fallback.
- [ ] **CCTS registration** — Step 12 is admin ToDo with detailed instructions and CCTS membership URL. Playwright automation planned for future release.
- [x] **eSign workflow for CRTC letter** — DONE. CRTC-specific eSign at `/portal/sign` (portal-esign.ts). Generic eSign at `/portal/esign/` (portal-esign-generic.ts) works for all doc types.
- [x] **CRTC letter email submission** — DONE (2026-05-04). After client eSign, letter auto-emailed to secretary.general@crtc.gc.ca from regulatory@{domain}.ca. Also included in physical binder.
- [ ] **BITS affidavit** — BITS requires a notarized affidavit confirming the company is a US carrier (or Canadian equivalent). Provider: NotaryLive ($59/mo platform + $23/session). Implement: generate affidavit DOCX → send NotaryLive session invite → on completion → attach to binder.
- [x] **Order confirmation email** — DONE. Telegram notification + email on payment via checkout.ts handlePaymentComplete().
- [ ] **Branded HTML email templates** — 15 ERPNext Email Notifications are plain text. Design and import HTML templates (header logo, PW brand colors, footer with unsubscribe).

### 1.4 Customer Portal Auth `[CRTC]`
- [x] **JWT portal authentication** — DONE. All portal pages use signed JWT tokens (72h expiry) passed via email links. Middleware at portalAuth.ts validates token → scopes to customer's order.
- [x] Auth middleware on all `/portal/*` API routes — requirePortalAuth middleware validates JWT, scopes by order_id + email.
- [x] Session via query param (email link), Bearer header (XHR), or cookie (pw_portal_token).

### 1.5 End-to-End CRTC Test `[CRTC]`
- [ ] Place a real CRTC order (numbered company, test customer)
- [ ] Walk through all 12 pipeline steps
- [ ] Confirm: DID provisioned, COLIN automation runs, domain registered, CRTC letter generated + eSigned, binder compiled, MinIO upload, customer emails received
- [ ] Document any failures and fix before first real customer order

---

## Track 2 — US Formation (Second Revenue Stream)

### 2.1 Wyoming End-to-End `[FORMATION]`
WY is the highest-priority state — selectors verified on name search, no CAPTCHA, cheapest filing fee.

- [ ] Implement WY filing automation: walk the WYO SOS filing wizard step-by-step with Playwright, map all form field selectors
- [ ] Wire payment step: WY SOS accepts credit card — use `relay-filing-card`
- [ ] Test: place a real WY LLC order, confirm name search works, confirm filing completes, confirm confirmation number saved to DB
- [ ] Wire formation worker to create ERPNext Formation Order + Sales Invoice on order receipt
- [ ] Wire formation worker to send order confirmation email after payment

### 2.2 Colorado End-to-End `[FORMATION]`
CO name search is already working (Socrata API). Need filing automation.

- [ ] Research CO SOS filing wizard (sos.state.co.us), map selectors
- [ ] Implement CO LLC filing automation
- [ ] Test end-to-end

### 2.3 Server-Side Fee Validation `[FORMATION][PLUMBING]`
Currently the API trusts client-submitted `state_fee_cents`. This is a billing vulnerability.

- [ ] Add server-side lookup: on order receipt, look up `state_fee_cents` from `state_filing_fees` DB table using `state` code
- [ ] Reject any order where client-submitted fee does not match DB value
- [ ] Audit and fix the 12+ known fee discrepancies between the frontend hardcoded array and DB (identified last session)

### 2.4 Operating Agreement + EIN `[FORMATION]`
- [ ] Test operating agreement DOCX generation end-to-end (template → DocxBuilder → PDF → MinIO)
- [ ] Test EIN obtainment worker (IRS Playwright) on a real WY entity after filing
- [ ] Confirm both documents are attached to the ERPNext Formation Order and emailed to customer

### 2.5 DocServer (Windows VM) `[FORMATION][INFRA]`
LibreOffice fallback produces lower-quality PDFs. Office 2021 on Windows is required for production-quality DOCX→PDF conversion.

- [ ] Provision Windows Server 2022 VM in Proxmox (2 vCPU, 4 GB RAM, 40 GB SSD)
- [ ] Install Microsoft Office 2021
- [ ] Deploy DocServer Flask app on port 5050
- [ ] Configure `DOCSERVER_URL` env var to point workers at Windows VM
- [ ] Verify: workers use DocServer when available, fall back to LibreOffice when not

### 2.6 Remaining State Adapters `[FORMATION]`
Priority order based on formation volume. Each requires: portal inspection, selector extraction, Playwright implementation, payment wiring.

- [ ] Delaware (DE) — CAPTCHA. Integrate 2captcha or AntiCaptcha solving service first.
- [ ] Florida (FL) — High demand. Playwright needed, no CAPTCHA expected.
- [ ] Texas (TX) — SOSDirect account needed for search. Get account first.
- [ ] Nevada (NV) — WAF. Use stealth Playwright (playwright-extra + puppeteer-stealth).
- [ ] Utah (UT) — Name search works without login. Filing needs UtahID OAuth.
- [ ] New Mexico, Ohio, Montana — Untested, research portals.
- [ ] Remaining 37 states — Batch: inspect portals, extract selectors, implement in priority order.

---

## Track 3 — Infrastructure & Ops

### 3.1 Production Deployment `[INFRA][BLOCKER]`
The rsync to production timed out. Get the current codebase deployed.

- [ ] Fix rsync: run with `--timeout=30` and `--compress`, or use a staged approach (`tar | ssh`)
- [ ] On server: `docker compose build && docker compose up -d`
- [ ] Update ERPNext Docker image: rebuild `performancewest-erpnext:latest` with latest `frappe_ca_registry` + `performancewest_erpnext` changes
- [ ] Run any pending DB migrations on production

### 3.2 Environment Variables `[INFRA]`
Several env vars are not yet set in production `.env`.

- [ ] `PORKBUN_API_KEY` / `PORKBUN_SECRET_KEY`
- [ ] `FLOWROUTE_ACCESS_KEY` / `FLOWROUTE_SECRET_KEY`
- [ ] `HESTIA_HOST` / `HESTIA_USER` / `HESTIA_PASSWORD`
- [ ] `SHKEEPER_API_KEY`
- [ ] `ADYEN_API_KEY` / `ADYEN_MERCHANT_ACCOUNT` / `ADYEN_CLIENT_KEY` (when account approved)
- [ ] `DOCSERVER_URL` (when Windows VM is provisioned)
- [ ] `CUSTOMER_JWT_SECRET` (for portal auth)

### 3.3 Monitoring & Alerts `[INFRA]`
- [ ] Set up UptimeRobot (free) to monitor: performancewest.net, api.performancewest.net, crm.performancewest.net, pay.performancewest.net
- [ ] Configure alert email to `ops@performancewest.net`
- [ ] Set up weekly automated DB backup to MinIO (pg_dump → minio/backups/)
- [ ] Verify ERPNext daily backup is configured and uploading to MinIO

---

## Track 4 — Payment & Billing Completion

### 4.1 Bundle / Compliance Checkout `[PLUMBING]`
The Express API returns `null` for bundle and compliance service checkout. These are broken.

- [ ] Implement bundle checkout in `api/src/routes/checkout.ts`: look up bundle from `service_bundles` table, create Sales Order + Invoice with correct line items and 20% bundle discount
- [ ] Implement compliance service checkout (CCPA audit, FLSA audit, etc.): create Sales Invoice with correct item and turnaround date
- [ ] Test both flows end-to-end through payment

### 4.2 Subscription / Renewal Billing `[PLUMBING]`
Annual renewals (RA $99/yr ($49 WY), Annual Report $99/yr, CRTC Maintenance $349/yr) exist as ERPNext Subscription Plans but the renewal worker is not wired to trigger them.

- [ ] Wire `renewal_handler.py`: on subscription due date (from Compliance Calendar), create ERPNext Sales Invoice + Payment Request, send renewal email with payment link
- [ ] Test: manually trigger a renewal for a test customer, confirm invoice created, confirm email sent, confirm payment marks subscription renewed

### 4.3 Refund Flow `[PLUMBING]`
- [ ] Verify ERPNext Credit Note flow works end-to-end
- [ ] Test: issue a partial refund on a test invoice, confirm Adyen processes the refund, confirm customer email is sent
- [ ] Document refund policy in customer-facing portal

---

## Track 5 — Docs Update

All 15 docs have stale content. These need to be rewritten to match current state before bringing on any contractors or making architectural decisions.

**Critical (do first):**
- [x] `docs/architecture.md` — Replaced Mautic with Listmonk. Added DocServer, workers, portal. Updated container count to 15.
- [x] `docs/go-live-todo.md` — Marked completed items. Added multi-province priority 11. Updated pricing.
- [x] `docs/formation-system.md` — Updated BC section with BITS/CCTS/GCKey/compliance. Updated DocServer.
- [x] `docs/crm.md` — Replaced Mautic Integration → Listmonk. Added CRTC campaign.

**High priority:**
- [x] `docs/product-facts.md` — Added Canada annual maintenance ($349), consulting ($75/hr), Canadian formation (C$449), maintenance bundles ($179/yr). Updated CRTC pipeline to 14 steps.
- [x] `docs/billing.md` — Added renewal billing, compliance calendar billing, maintenance bundles, Canadian formation pricing.
- [x] `docs/infrastructure.md` — Replaced Mautic/MySQL with Listmonk. Updated container count to 15. Added portal nginx config.

**Medium priority:**
- [ ] `docs/marketing.md` — Add FCC RMD as primary lead gen channel. Add Listmonk drip campaign details. Add 3 campaign descriptions.
- [x] `docs/state-automation-status.md` — Updated BC section with all new capabilities + GCKey automation table.
- [ ] `docs/document-generation.md` — Update DocServer section (MinIO transport, not HTTP). Add template list.

---

## Track 6 — frappe_registry Migration (Future)

This is a significant refactor — ~24 hours of work. Do not start until CRTC pipeline is working end-to-end and at least WY formation is live.

- [ ] Rename `frappe_ca_registry` → `frappe_registry`
- [ ] Generalize `CA Filing Request` DocType to `Registry Filing Request` (55 jurisdictions)
- [ ] Add `Registry Settings` DocType: NWRA defaults, per-state RA override, CA mailbox config
- [ ] Move 51 US state adapters from `scripts/formation/states/` into Frappe app
- [ ] Add extra-provincial registration support (10 Canadian provinces)
- [ ] Add foreign state qualification support (US foreign entity)
- [ ] Define `BaseAdapter` interface with auto-discovery by jurisdiction code
- [ ] Build fee scraper framework:
  - [ ] `base_scraper.py` — abstract scraper class with rate limiting, retry, user-agent rotation
  - [ ] `pdf_scraper.py` — Tesseract OCR for scanned fee schedule PDFs, pdfplumber for text PDFs
  - [ ] `fee_change_detector.py` — compares scraped values against DB, generates diff report
  - [ ] Admin email alert when any fee changes (does NOT auto-update — human review required)
  - [ ] Monthly cron job in workers container (`0 6 1 * *` — 1st of each month, 6am)
  - [ ] Store scraped results in `state_fee_scrape_history` table with timestamp + source URL + old/new values
- [ ] Implement US state fee scrapers (51 jurisdictions):
  - [ ] Tier 1 (top 10 by formation volume): WY, DE, FL, TX, NV, CA, CO, NY, GA, IL
  - [ ] Tier 2 (remaining 41 states + DC)
  - [ ] Handle: annual report fees, franchise taxes, business license fees, privilege taxes, personal property returns
  - [ ] Reference: `docs/state-annual-fees-complete.md` for current verified values + source URLs
- [ ] Implement Canadian province fee scrapers (10 provinces):
  - [ ] BC, AB, ON, QC, MB, SK, NS, NB, PE, NL
  - [ ] Scrape in CAD, convert to USD via `fx.ts` Bank of Canada rate + 10% buffer
- [ ] Fix fee data integrity: DB as single source of truth, frontend fetches from API, server-side validation (see 2.3)

---

## Launch Readiness Checklist

Before accepting the first paying customer:

- [ ] ERPNext payment gateways configured (Track 1.1)
- [ ] CRTC end-to-end test passes (Track 1.5)
- [ ] Portal authentication implemented (Track 1.4)
- [ ] Order confirmation email sends after payment (Track 1.3)
- [ ] Production deployed with current codebase (Track 3.1)
- [ ] All env vars set in production (Track 3.2)
- [ ] UptimeRobot monitoring configured (Track 3.3)

Formation orders can follow ~2 weeks after CRTC is live (WY first).

---

## Open Questions / Decisions Needed

| Question | Context | Deadline |
|----------|---------|----------|
| Adyen account approval status? | Without Adyen, card/ACH payments are blocked. Only crypto works. | Before first customer |
| NotaryLive account — go or no-go? | $59/mo + $23/session for BITS affidavit notarization. Alternative: partner with a Canadian notary. | Before first CRTC delivery |
| Anytime Mailbox credential handoff policy? | Provider confirmed no API; Playwright signup uses shared inbox OTP. Decide whether to bootstrap with `filings@performancewest.net` then transfer to client, or require client email from the start. | Before first customer |
| Portal auth strategy? | ERPNext portal login vs. signed JWT in email link. Signed JWT is simpler, no password needed. | Before first customer uses portal |
| Canadian accountant? | 3 hours free accounting support is promised in the CRTC package. Need to onboard a freelance Canadian accountant. | Before first delivery |
