solar/docs/MONETIZATION_PHASE1_DESIGN.md

9.5 KiB
Raw Permalink Blame History

Phase 1 monetization design — B2B Pro (#1) + Developer API (#10)

This document specifies how to extend solar_trading_engine with subscriptions, API keys, entitlements, and usage metering, while keeping the public “lite” app as lead generation.


1. Launch revenue model (pick one primary, one secondary)

Recommendation for v1: Stripe as the billing source of truth (better fit than Paddle if you already host the backend in the EU and want maximum control over webhooks and DB sync).

1.1 Two sellable surfaces

Offering Buyer Price anchor (from BUSINESS_OPPORTUNITIES.md) What they pay for
Pro (B2B SaaS) Teams / trading desks €299499/month Full dashboard + higher limits + paid features (see §4)
Developer API Integrators / startups Usage-based + floor Programmatic access, SLAs, high call volume

Launch packaging (concrete):

  1. Stripe Product: “Degelas Pro”

    • Price: one monthly price (e.g. €399/month) in EUR.
    • Quantity: 1 per organization (not per user), unless you later add seats.
  2. Stripe Product: “Degelas API” (can be add-on or standalone)

    • Option A — Included + overage: Base €99/month includes N API calls (e.g. 100k); overage per 1k calls via Stripe metered billing or invoice line items.
    • Option B — Pure usage: No base; bill monthly on metered usage (harder for predictability; better for dev-first).

Suggestion: Start with Option A for API: predictable minimum revenue + overage aligns with “passive” operations.

1.2 What Stripe must store (synced to DB)

  • stripe_customer_id → Organization
  • stripe_subscription_id + status + current_period_end
  • price_id / product mapping → internal plan enum: free | pro | api_addon
  • Webhooks: customer.subscription.*, invoice.paid, invoice.payment_failed, checkout.session.completed

2. Identity & accounts

2.1 Goals

  • Subscriptions attach to an Organization (B2B).
  • Users belong to orgs; one user can belong to one org in v1 (simplify).
  • Session for browser dashboard: cookie or JWT from your IdP.
  • API keys belong to Organization (or User with org scope); never use raw user passwords for API access.

2.2 Implementation options (choose one)

Approach Pros Cons
Clerk (or similar) Google + magic link fast; orgs feature Vendor lock-in, cost
Supabase Auth Postgres nearby; social providers Another stack piece
Custom (JWT + magic link via Resend/SendGrid + optional Google OAuth) Full control More code to secure

Recommendation: Use Clerk or Supabase if time-to-market matters; use custom only if you must keep everything in the existing FastAPI repo with minimal deps.

2.3 Minimal schema (logical)

Organization
  id, name, created_at
  stripe_customer_id (nullable)
  plan: enum free | pro | api  -- denormalized from subscription for fast checks
  subscription_status: active | past_due | canceled | trialing | null

User
  id, org_id (FK), email, role: owner | member
  external_auth_id (nullable) -- sub from Clerk/Supabase

ApiKey
  id, org_id (FK)
  name (e.g. "production")
  key_prefix (e.g. "dg_live_abc")  -- show in UI once
  key_hash (bcrypt/argon2 of secret part)
  scopes: json or bitmask
  created_at, revoked_at

ApiUsageMonthly   -- or daily rollup + monthly aggregation
  id, org_id, year_month (YYYY-MM), api_calls, -- increment per request
  optional: breakdown by route_group

3. Entitlements (what “Pro” and “API” unlock)

Use one internal module (e.g. app/entitlements.py) that maps plan + subscription_status → limits. Env vars override defaults for ops (e.g. PRO_MAX_COUNTRIES=5).

3.1 Suggested limits

Capability Public lite (anonymous / logged free) Pro API key (Developer)
Countries per session 1 (current behaviour) e.g. up to 5 or all N/A (key is not “country UI”; scope by quota)
Locations visible All in country (today) Same or extended By location_id in request
GET /history window e.g. last 90 days e.g. 365+ or full DB Same as Pro for same org
GET /hourly limit param cap 168 (1 week) 744+ Same
Trading / backtest / forecast-eval 403 or heavy rate limit Full Full via API key
POST /metrics/refresh-all-locations 403 Allowed (with cooldown) N/A (server-side jobs)
POST /backfill 403 Optional allow 403
GET /reports/daily-metrics 403 or watermarked Allowed Allowed
POST /data/load-all 403 Owner-only 403

Tune numbers after you see real usage; the important part is having the gates.

3.2 Route classification (FastAPI)

Implement dependencies:

  • optional_user — resolves session if present; else anonymous.
  • require_pro — Pro org with subscription_status in (active, trialing).
  • require_api_keyAuthorization: Bearer dg_live_... or X-Api-Key: ...
  • check_api_quota — after auth, increment meter + reject with 429 if over.

Public marketing (no auth): GET /countries, GET /locations, GET /version, GET /health, POST /track (optional throttle by IP).

Lite dashboard (no paywall): GET /metrics/all with strict query caps (past_days<=3, forecast_days<=14), GET /config (maybe hide sensitive fields for anonymous).

Pro-only or key-only: everything under /trading/* (except maybe read-only demos), extended history/hourly, deepdive, reports, admin POSTs.

Note: Today many routes are open. The design is to add dependencies incrementally without breaking production: ship with MONETIZATION_ENABLED=0 until Stripe + auth are live.


4. Metering (API #10)

4.1 What counts as “one call”

  • 1 request = 1 billable unit for successful responses (2xx). Optionally 0.1 unit for 304/429 — keep it simple: count all completed requests excluding health/version.

  • Weighting (optional v2): GET /metrics/all counts as W units (e.g. 5) because it fans out locations.

4.2 Storage

Store Use case
PostgreSQL (api_usage_daily) Source of truth for invoicing disputes; simple INSERT ... ON CONFLICT DO UPDATE per org+day
Redis (INCR api:{org_id}:{yyyy-mm-dd}) Fast rate limiting + near-real-time dashboard; flush to Postgres nightly or on threshold

Recommendation: Postgres-only for v1 if traffic is moderate; add Redis when you need sub-second burst limits.

4.3 Overage

  • At period end (or daily cron), compare usage vs included calls from Stripe subscription item.
  • Report overage to Stripe via Usage Records API (metered price) or generate invoice line via webhook job.

5. API key format & security

  • Prefix: dg_live_ / dg_test_ (test mode Stripe).
  • Secret: high-entropy suffix; store hash only.
  • Rotation: allow multiple keys per org; revoke sets revoked_at.
  • Scopes (v1): metrics:read, trading:read, trading:write — map to route prefixes.

6. Frontend changes (high level)

  • Marketing site / lite: unchanged flow; show upgrade CTAs on locked features.
  • Account: /account/billing → Stripe Customer Portal (subscription manage).
  • Developer: /account/api-keys → create/revoke keys, show usage this month.

Use Stripe Checkout or Stripe Elements for first purchase; Customer Portal for self-service.


7. Environment flags

MONETIZATION_ENABLED=0          # 1 = enforce entitlements
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRICE_PRO_MONTHLY=
STRIPE_PRICE_API_BASE_MONTHLY=
STRIPE_PRICE_API_METERED=       # if using metered overage
AUTH_PROVIDER=clerk|supabase|custom

8. Implementation order (build sequence)

  1. DB migration: organizations, users, api_keys, api_usage_daily (or monthly).
  2. Auth integration (session → org_id).
  3. Stripe Checkout + webhook → create/update org + plan.
  4. Entitlement dependency + gate POST admin routes first (backfill, refresh-all, data/load-all).
  5. API keys + middleware + Postgres metering.
  6. Lite caps on history/hourly/metrics query params for anonymous/free.
  7. Frontend: login, billing portal, upgrade banners.
  8. MONETIZATION_ENABLED=1 in staging → production.

9. Out of scope for Phase 1

  • Paddle (unless you need MoR tax handling globally — then re-evaluate).
  • Per-seat pricing (add later).
  • SLA / dedicated infra (commercial, not code).
  • Full org multi-tenant data isolation for metrics rows (today data is global per location_id; for strict B2B you may later namespace by customer — not required for billing v1).

10. Success criteria

  • Pro customer can pay, access gated routes, and manage subscription in portal.
  • API customer can create key, see usage, and get 429 when over quota.
  • Anonymous users still get lite dashboard without payment.
  • Webhooks keep plan and subscription_status in sync with Stripe.

This file is the blueprint for implementation tasks in the repo; adjust limits and prices as you validate with pilots.