Billing Module
The billing module manages subscription tiers, credit-based usage accounting, and Stripe integration. It lives in src/mods/billing/ and is server-only (gated behind #[cfg(not(target_family = "wasm"))]) except for shared types and UI components.
Architecture
Section titled “Architecture”Billing follows a four-pool credits-only waterfall:
- Op-type credit pool — per-operation credits included in the tier (e.g. 9000 voice credits/month), stored in
org_budget.*_included - Included credits — general credit pool bundled with the tier, replenished each billing cycle
- Purchased credits — top-up credit packs that persist across cycles
- Overdraft — drives
included_creditsnegative up to the org’s overdraft limit
All pools store and deduct credits. Native units (minutes, segments, tokens) are converted to credits once via billing_config.*_credits rates before the waterfall starts. If all pools are exhausted and the overdraft limit is reached, the operation is rejected.
Module Structure
Section titled “Module Structure”src/mods/billing/├── api/ # Dioxus server functions│ ├── list_subscription_tiers # GET /api/billing/tiers (public)│ ├── get_org_subscription # GET /api/billing/subscription│ ├── create_checkout_session # POST /api/billing/checkout (owner-only)│ └── create_billing_portal # POST /api/billing/portal (owner-only)├── components/│ └── billing_section # Settings page billing UI├── conf/│ └── stripe_core_conf # Stripe keys from core_conf table├── routes/│ └── stripe_webhook_route # POST /api/billing/stripe/webhook├── services/│ ├── stripe_client # Shared Stripe API client (OnceCell)│ ├── create_stripe_customer # Creates Stripe Customer on signup│ ├── create_checkout_session # Builds Stripe Checkout Session│ ├── create_billing_portal # Builds Stripe Billing Portal Session│ ├── dispatch_stripe_event # Webhook event router│ ├── record_operation # Waterfall deduction engine│ ├── check_operation_allowed # Lightweight pre-flight check│ └── reset_cycle_budget # Replenishes credits on invoice.paid└── types/ # Shared domain types (WASM-safe)Database Tables
Section titled “Database Tables”| Table | Purpose |
|---|---|
billing_config | Single-row global credit pricing for each operation type |
subscription_tier | Plan catalog (slug, name, price, included allowances, feature flags) |
organization_subscription | One-per-org active subscription (tier, status, seats, period) |
org_budget | Per-org credit balances (included + purchased pools, free allowance counters) |
operation | Append-only audit log of every billable event |
credit_pack | Available top-up packs |
credit_pack_purchase | Purchase history linking orgs to packs |
The organization table gains a stripe_customer_id column. The core_conf table gains stripe_secret_key and stripe_webhook_secret.
Stripe Customer Creation
Section titled “Stripe Customer Creation”Every new organization gets a Stripe Customer at signup. This happens inline — before the org row is committed — so a Stripe failure rolls back the entire signup.
Both signup paths create the customer:
- Email/password —
signup_api.rscallscreate_stripe_customer()after creating the user - OAuth —
create_organization_with_twilio_servicereceives the OAuth email and creates the customer inline
The Stripe Customer’s metadata.organization_id links it back to the Loquent org.
Environment Variables
Section titled “Environment Variables”| Variable | Required | Description |
|---|---|---|
STRIPE_SECRET_KEY | For billing | Stripe API secret key, seeded into core_conf |
STRIPE_WEBHOOK_SECRET | For billing | Webhook signing secret for signature verification |
Both are stored in core_conf and loaded via StripeCoreConf. Missing keys don’t crash the app — billing services return clear errors when invoked without configuration.