Billing Service — Overview
The Billing Service is the subscription and usage-limits engine for the platform. It is an optional service — the platform runs fully without it. When not deployed (e.g. self-hosted or behind a private license model), the API Gateway skips all limit checks and subscription status validation, allowing all tenant requests through.
Zero-state guarantee: When a new tenant is created, the Billing Service automatically creates a subscription in
trialingstatus. There is no ambiguous "no subscription" state — every tenant always has an active subscription record.
Technical Stack
| Component | Technology | Detail |
|---|---|---|
| Language | Go | pgxpool + pgx/v5 + sqlc (sql_package: pgx/v5) |
| Database | PostgreSQL | Plans, subscriptions, usage, webhook events |
| Amounts | shopspring/decimal | All monetary amounts stored in cents |
| Payment provider | Stripe (stripe/stripe-go) | Or custom internal engine |
| Cache | Valkey | plan_info:{tenantId} — subscription status + limits, TTL 15 min |
| Events | Kafka | Publishes billing.subscription.changed, billing.plan.changed |
Internal Packages
| Package | Responsibility |
|---|---|
internal/config | Environment-based configuration |
internal/handler | HTTP request handlers |
internal/model | Domain models: Plan, Subscription, Usage |
internal/producer | Kafka event producer |
internal/repository | PostgreSQL repositories (sqlc-generated) |
internal/service | Business logic: plan changes, lifecycle, limit enforcement |
internal/webhook | Stripe Stripe-Signature verification (stripe.ConstructEvent()) |
internal/worker | Background workers: escalation timeline, trial expiry |
gRPC Service: 14 RPCs
All billing operations are exposed as gRPC RPCs, consumed by the API Gateway and internal services. The Gateway translates REST calls to these RPCs.
| Group | RPCs |
|---|---|
| Plan CRUD | CreatePlan · RetrievePlan · ListPlans · UpdatePlan |
| Subscription lifecycle | CreateSubscription · RetrieveSubscription · ListSubscriptions · UpdateSubscription · CancelSubscription · CancelDowngrade |
| Usage & limits (hot path) | RetrieveUsage · CheckLimit |
| Webhook processing | HandleWebhook |
CheckLimit is the critical hot-path RPC. It is called by the
Gateway on every mutating request when Billing is active. Its
response is cached in Valkey — the gRPC call is only made on a
cache miss (see Usage Limits).
Subscription Lifecycle Summary
Tenant created → trialing (automatic, zero-state)
│
├─ Payment method added → active
├─ Trial expires, no payment method → past_due → escalation timeline
│
└─ active
│
├─ Payment success → active (continues)
├─ Payment failure → past_due → (7d) → suspended → (30d) → terminated
└─ Manual cancel → canceled
Status definitions:
| Status | Access | Notes |
|---|---|---|
trialing | Full | Auto-created on tenant registration. No payment method required. |
active | Full | Subscription in good standing. |
past_due | Full (grace period) | Payment failed. Tenant continues with full access for up to 7 days. |
suspended | Read-only | 8–37 days past due. POST/PATCH/DELETE blocked (except Money operations). |
terminated | None | 38+ days past due. All requests blocked. Async soft-delete starts. |
canceled | None | Manually canceled. |
Billing Permissions
| Permission | Who | Description |
|---|---|---|
billing.plan.change | Owner, Admin | Change subscription plan (financial impact) |
billing.payment.manage | Owner, Admin | Add/remove payment methods |
billing.info.update | Owner, Admin | Update billing email, address |
billing.view | All authenticated | View invoices and usage (read-only) |
billing.export | Owner, Admin | Export billing data |
REST API (via Gateway)
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/billing/plans | List available plans |
GET | /api/v1/billing/plans/:id | Get plan details |
GET | /api/v1/billing/subscriptions/current | Get current tenant subscription |
PATCH | /api/v1/billing/subscriptions/:id | Change plan (requires version for optimistic locking) |
DELETE | /api/v1/billing/subscriptions/:id | Cancel subscription |
POST | /api/v1/billing/subscriptions/:id/cancel-downgrade | Cancel a pending downgrade |
GET | /api/v1/billing/usage | Current usage vs limits |
POST | /api/v1/billing/webhooks/{provider} | Receive webhook from payment provider |
Optional Billing: Self-Hosted Mode
When the Billing Service is not deployed:
Gateway limit check:
→ Is BILLING_SERVICE_ENABLED=false?
→ Yes → skip plan_info:{tenantId} lookup
skip subscription status check
skip limit enforcement
forward request to upstream directly
All tenants have unlimited access to all platform features. There are no subscription records. The platform behaves as an unrestricted engine. This is the default behavior for self-hosted deployments that do not require monetization.
Related Pages
- Subscriptions — lifecycle, plan change state machine, optimistic locking, downgrade grace period
- Usage Limits — Gateway auto-check, Valkey cache, limit types, 402 Payment Required, FinCEN Money exemption
- Escalation — grace period, suspension, termination, async soft-delete, instant reactivation on payment
- Webhooks — Stripe webhook processing, signature validation, idempotency, retry policy