Skip to content

Instantly share code, notes, and snippets.

@vegarsti
Created March 16, 2026 14:10
Show Gist options
  • Select an option

  • Save vegarsti/c29ec1002f29c1c6a046a7f64a4f8437 to your computer and use it in GitHub Desktop.

Select an option

Save vegarsti/c29ec1002f29c1c6a046a7f64a4f8437 to your computer and use it in GitHub Desktop.

Elwing Billing System — Data Flow Summary

Elwing uses Polar (polar.sh) as its billing/subscription provider. Billing is credit-based: users purchase subscriptions or top-ups via Polar, receive credits, and credits are consumed as the AI agent processes emails, chats, and memory operations.

Key Concepts

Concept Description
Credits Internal currency. Converted from token cost: credits = tokenCents × 12.5
Billing Owner Each space has one billing owner (user). All charges in that space bill to them.
Billing Account Per-user row (user_billing_account) tracking credit_balance_credits, current_debt_credits, max_overage_credits
Charge Ledger Append-only log of every credit deduction (billing_charge_ledger)
Subscription Credits Ledger Append-only log of every credit grant (subscription_credits_ledger)

Data Flow

1. User Sign-Up → Polar Customer Provisioning

User registers → ensurePolarCustomerForUser()
  → Creates Polar customer (externalId = userId)
  → Creates local user_billing_account row
  → Optionally grants 5,000 initial credits (negative-amount event ingested to Polar meter)

2. Subscription / Top-Up Purchase

User clicks Subscribe → createPolarCheckoutUrlForUser()
  → Redirects to Polar-hosted checkout page
  → On success, Polar fires subscription/meter events

User clicks Top-Up → createPolarTopupCheckoutUrlForUser(productId)
  → Same flow, one-time product

3. Credit Grants (Polar → Elwing)

Polar emits events (subscription.created, meter.credited, order.paid, etc.)
  ↓
syncSubscriptionEventsForUser() — called by cron or on-demand
  → Fetches all events from Polar Events API (paginated, since last sync)
  → For each meter.credited event:
      recordSubscriptionCredits() → INSERT into subscription_credits_ledger
                                  → UPDATE user_billing_account.credit_balance_credits += N
  → Updates last_synced_polar watermark

4. Credit Consumption (Elwing → Polar)

Agent processes a trigger (email, chat, cron, memory op)
  ↓
getChargeableForSpace(context)
  → Looks up space → billing owner → billing account
  → Checks remaining = balance + maxOverage − debt
  → If remaining ≤ 0 → throws OutOfCreditsError
  → Returns chargeCredits() callback
  ↓
After AI call completes:
  chargeCredits(usedCredits, tokenCents)
  → createBillingCharge() → INSERT into billing_charge_ledger
                           → UPDATE user_billing_account (deduct balance, accrue debt if needed)
  → ingestChargeToPolar() → POST to Polar Events API (event: "lefos_credits_used")
  → markBillingChargePolarSynced()

5. Catch-Up Sync (background)

startBillingSyncScheduler() — runs every 60s
  → listUnsyncedBillingCharges() (polar_synced_at IS NULL)
  → For each: ingestChargeToPolar() → markBillingChargePolarSynced()

This ensures charges that failed to sync inline (network errors, etc.) are eventually delivered.

6. Out-of-Credits Handling

OutOfCreditsError thrown
  ↓
Before giving up, replayPolarEventsForSpaceBillingOwner()
  → Re-syncs latest Polar events (in case new credits arrived)
  → If still out: sendOutOfCreditsReplyOncePerDay()
      → Sends email to requester (rate-limited 1/day via Redis)
      → Billing owner gets link to /account/billing
      → Non-owner gets "contact your garden owner" message

Trigger Types Billed

email · cron · chat · message · memory-extraction · memory-compaction · memory-reflection · herald-chat

DB Tables

  • user_billing_account — per-user balance, debt, overage cap, Polar sync cursor
  • billing_charge_ledger — every deduction (idempotent by key)
  • subscription_credits_ledger — every credit grant (idempotent by key)
  • space_billing_config — maps space → billing owner user
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment