Skip to content

Add Autumn prepaid billing#390

Merged
breardon2011 merged 7 commits into
mainfrom
prepaid-billing
Jun 23, 2026
Merged

Add Autumn prepaid billing#390
breardon2011 merged 7 commits into
mainfrom
prepaid-billing

Conversation

@breardon2011

@breardon2011 breardon2011 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Autumn prepaid billing

Replaces the legacy free-trial/credit system with prepaid credits on
Autumn. A new org gets $5, tops up at the same
per-second rates, can opt into auto-recharge, and subscribes to
concurrency tiers as a separate paid axis (Base 5 free / Pro 100 / Pro+
600 / Pro++ 1000 / contact-us). Credit ledger, metering, top-ups,
auto-recharge, and concurrency plans all live in Autumn; we keep only
hot-path projections.

TL;DR for reviewers (the 3 things people ask)

  • Where's the billing authority? Autumn is the source of truth. We mirror
    only two fields into D1 — is_halted and max_concurrent_sandboxes — so the
    create/wake gates never make a synchronous Autumn call.
  • Does this bill from each cell? No — the opposite. Billing is
    centralized at the edge. The cell-side change in cmd/server is a
    deletion: it removes the old per-cell AutumnReporter. The cell now
    only executes a halt the edge dispatches to it (/admin/halt-org) — it
    never reports billing.
  • Does this modify the cells table? No. The only schema change is on
    orgs (billing_provider, autumn_usage_watermark). The UPDATE cells in
    the diff is the pre-existing capacity reporter from the multi-cell PR; the
    edge only reads cells for routing.

Dormant on merge

No-op until activated. Only orgs with billing_provider='autumn' take the
Autumn path; everyone else stays on legacy. New signups go to Autumn only when
AUTUMN_NEW_ORGS=true, and the meter no-ops unless AUTUMN_SECRET_KEY is set.
Existing orgs migrate later via a deliberate, notified process — no org is
ever on both billers.

How billing flows — one source, one biller per provider

This reuses the existing edge-authoritative billing model. Both legacy and
autumn read the same single source (usage_samples in D1); they differ
only in which biller drains it:

Provider Source Biller (where billing happens) What the cell does
Legacy/unified usage_samples (D1) billing-rollup worker → Stripe nothing (billing is at the edge)
Autumn usage_samples (D1) autumn-meter edge cron → Autumn only hibernates running boxes when the edge dispatches a halt
  • events-ingest lands autumn usage into usage_samples (same rows legacy uses).
  • billing-rollup excludes autumn from its Stripe aggregation → no double-bill.
  • autumn-meter (one worker, 5-min cron) tracks per-tier usage to Autumn off a
    per-org watermark; on exhaustion it calls projectOrg (sets is_halted,
    dispatches a hibernate to the owning cell).
  • Svix webhooks keep is_halted/max_concurrent projected to D1; gates read the
    projection with an Autumn self-heal. Resume is unblock-only (no auto-wake).

Free tier is going away, so autumn orgs are exempt from the legacy free-tier
size ceiling: memory/CPU unrestricted (metered per GB-second), disk bounded by
per-org max_disk_mb (default 20GB, raised on request). The cap-token carries
billing_provider so cell and edge agree.

Billing page

Branches on billing_provider; legacy orgs keep the legacy UI. Prepaid UI:
credits balance, per-sandbox usage breakdown, top-up, concurrency tiers,
auto-recharge. Card capture + the auto-recharge arming charge are handled
server-side (Autumn won't fire auto-recharge against a saved-but-never-charged
card, so enabling runs the first recharge once; idempotent, no browser fragility).

Validated on dev (end-to-end)

  • Signup → autumn + $5 + base; concurrency 5→429, Pro→100; legacy regression OK
  • Large autumn sandbox (16GB/4vCPU → 201; 40GB disk → 403)
  • Metering: real usage → Autumn balance decrement → inline halt + hibernate → create 402
  • Edge↔cell usage parity within 0.007% (prod spot-check)
  • Top-up, concurrency subscribe, auto-recharge (enable → one $25 charge → fires on its own)

Prod prerequisites (before/with deploy)

  1. Apply schema_phase7.sql to opencomputer-prod D1 — adds billing_provider
    • autumn_usage_watermark (additive, default legacy/0). Must precede the
      worker deploy
      (loadOrgPolicy / metering / billing SELECT billing_provider).
  2. CF_ADMIN_SECRET + EVENT_SECRET already on the prod edge. At activation:
    set AUTUMN_SECRET_KEY + AUTUMN_WEBHOOK_SECRET, register the Autumn webhook,
    flip AUTUMN_NEW_ORGS=true. Cron triggers are already in wrangler.prod.toml
    (no-op until the key is set).

Not in this PR (follow-ups)

  • Activation (flipping AUTUMN_NEW_ORGS) — deliberate, separate.
  • Legacy→autumn per-org migration tool — pulled from this PR (was unverified);
    reintroduced when migrations are run + tested. Auto-recharge stays opt-in (ROSCA)

@breardon2011 breardon2011 marked this pull request as ready for review June 19, 2026 03:01
@breardon2011 breardon2011 marked this pull request as draft June 22, 2026 19:20
Brian Reardon and others added 2 commits June 22, 2026 18:32
…puter into prepaid-billing

# Conflicts:
#	cloudflare-workers/api-edge/src/autumn_webhook.ts
#	cloudflare-workers/api-edge/src/dashboard.ts
#	cloudflare-workers/api-edge/src/index.ts
#	cloudflare-workers/schema_phase6.sql
#	cmd/server/main.go
#	internal/db/usage.go
#	web/src/api/client.ts
#	web/src/pages/Billing.tsx
@breardon2011 breardon2011 marked this pull request as ready for review June 23, 2026 03:05
Brian Reardon added 2 commits June 22, 2026 20:10
Both are isolated (nothing references them): the migration CLI is an
unverified ops helper for a later deliberate per-org cutover, and the
dev script is a local billing/parity test aid. Reintroduce the migrate
tool when migrations are actually run + tested.
# Conflicts:
#	cloudflare-workers/schema_phase6.sql
#	web/src/pages/Billing.tsx
@breardon2011 breardon2011 merged commit 6ed835c into main Jun 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants