# Secondary Market — Hybrid subscription + intelligence credits (handoff spec)

**Purpose:** Brief for design, QA, and engineering on the **Secondary Market** codebase (separate from Geospatial). Reuse the **same product logic** as CityZeen Geospatial: **subscription as anchor** + **metered credits** for variable-cost actions + **optional top-ups**.

**Reference implementation (UI + client demo):** Geospatial-AI-listings — `cz-credits.js`, Settings “Intelligence credit pool” + credits-by-tool table, header chip, Ask AI debit. Treat as **behavioral reference**, not a copy-paste of `localStorage` for production.

---

## 1. Business model (locked)

| Layer | Role |
|--------|------|
| **Subscription** (Base / Intelligence / Professional) | Predictable MRR, feature gates, **included monthly credit grant** (amounts TBD to match your pricing). |
| **Credit balance** | Deducted per **billable action**; user sees balance before expensive operations. |
| **Soft cap** | e.g. `3 × monthly_included` on carryover to avoid unbounded accrual (tune for product). |
| **Monthly grant** | On calendar month rollover, add included grant, cap to soft cap. |
| **Top-ups** | Paid packs (Stripe) → ledger + balance; **Odoo** for ops/finance (align with org `.cursorrules`). |
| **Demo** | May use browser storage only with clear “demo” labels until API exists. |

---

## 2. What to meter on Secondary Market

Public matrix (“Cost in credits per tool”). See `lib/credits-config.js` and `GET /api/credits-matrix`.

---

## 3. Engineering requirements

**Must have (production path)**

- Balance and ledger **per authenticated user** on the **server** (Odoo `crm.lead` rows with prefix `CZ_SMCR|`, `expected_revenue` as signed credit delta).
- **Idempotent** debit (`name` = `CZ_SMCR|debit|<idempotencyKey>`).
- **Feature flag:** `CZ_CREDITS_ENFORCE=1` vs warn-only when unset/`0`.
- **Clerk JWT** required for server mutations; unauthenticated clients get demo UX only.

**Should have**

- Admin/support view: last N ledger events (via `GET /api/credits-state` → `ledgerTail`).

**Nice to have**

- `pageshow` refetch in client for multi-tab.

---

## 4. UX acceptance criteria

1. Header chip: balance + link to Account credits block; tooltip with plan / cap / included.
2. Account: plan selector, matrix, top-up CTA (Stripe when keys set).
3. Insufficient credits: blocked with required credits + top-up path.
4. Plan change: server trims balance to new soft cap; upgrade may grant delta (see `credits-plan`).

---

## 5. Environment variables (production)

| Variable | Purpose |
|----------|---------|
| `ODOO_*` | Existing Odoo RPC (ledger rows). |
| `CLERK_SECRET_KEY` | Verify Bearer tokens for credits APIs. |
| `STRIPE_SECRET_KEY` | Checkout + session confirm. |
| `CZ_CREDITS_ENFORCE` | `1` = block when insufficient; `0` = warn / allow demo path. |
| `CZ_PUBLIC_APP_URL` | Success URL base for Stripe Checkout (e.g. `https://app.cityzeen.co`). |
| `CZ_REGISTRY_ADMIN_SECRET` | **Gate 2:** `POST /api/action-registry-admin` requires header `x-cz-registry-admin` with this value. |
| `CZ_TENANT_ID` | Optional tenant id in registry JSON (default `cityzeen`). |

---

## 6. One-line pitch for execs

*Secondary Market keeps **subscription for relationships and depth**, and **credits** so power traders pay for **compute-heavy and data-heavy** usage without punishing light users on list price.*
