Introduction
Modern billing engine for SaaS applications in Go.
Ledger is a Go library for building usage-based billing systems for SaaS applications. Instead of managing complex pricing logic, you define plans, track usage, check entitlements, and generate invoices — and Ledger handles the billing orchestration at runtime.
Ledger is a library — not a service. You bring your own database, payment provider, and HTTP server. Ledger provides the billing engine plumbing.
The Billing Model
Ledger models SaaS billing the way modern applications need it:
| Component | What it represents | Go type |
|---|---|---|
| Plan | What you sell (features + pricing) | plan.Plan |
| Feature | What customers can use (seats, API calls, storage) | plan.Feature |
| Subscription | What customers have purchased | subscription.Subscription |
| Meter | Usage tracking (API calls, storage, etc.) | meter.UsageEvent |
| Entitlement | Permission checks (sub-millisecond latency) | entitlement.Result |
| Invoice | What customers owe | invoice.Invoice |
| Coupon | Discounts and promotions | coupon.Coupon |
| Provider | Payment gateway integration | provider.Provider |
What it does
- Usage-based billing — Track metered usage with high-throughput batched ingestion (10K+ events/sec).
- Sub-millisecond entitlements — Check feature access with sub-millisecond latency using intelligent caching.
- Flexible pricing — Graduated tiers, volume pricing, per-seat, flat-rate, and hybrid models.
- Subscription lifecycle — Trials, upgrades, downgrades, cancellations, and proration.
- Invoice generation — Automated invoice creation with line items, taxes, and discounts.
- Multi-tenancy — Built-in tenant isolation with context propagation.
- Plugin system — 16 lifecycle hooks for metrics, audit trails, webhooks, and custom logic.
- TypeID everywhere — All entities use type-prefixed, K-sortable identifiers (
sub_,inv_,pln_, etc.). - Integer-only money — No floating-point precision issues with currency amounts.
- Multiple stores — PostgreSQL, SQLite, Redis, and in-memory implementations.
- Provider ready — Integration points for Stripe, Paddle, and custom payment gateways.
Design philosophy
Library, not service. Ledger is a set of Go packages you import. You control main, the database connection, and the process lifecycle.
Performance first. Entitlement checks in under 1ms with caching. Usage ingestion at 10K+ events/second. Invoice generation in under 100ms.
Type-safe money. All currency amounts use integer cents with the types.Money type. No floating-point errors.
Context-driven tenancy. context.Context carries tenant and app IDs, enforced at every layer.
Pluggable everything. Every subsystem defines a Go interface. Swap any storage backend or provider with a single type change.
Quick look
package main
import (
"context"
"log"
"time"
"github.com/xraph/ledger"
"github.com/xraph/ledger/plan"
"github.com/xraph/ledger/store/postgres"
"github.com/xraph/ledger/subscription"
"github.com/xraph/ledger/types"
)
func main() {
ctx := context.Background()
// Create the store
store := postgres.New(pool)
// Build the billing engine
engine := ledger.New(store,
ledger.WithMeterConfig(100, 5*time.Second),
ledger.WithEntitlementCacheTTL(30*time.Second),
)
// Start the engine
if err := engine.Start(ctx); err != nil {
log.Fatal(err)
}
defer engine.Stop()
// Create a plan
p := &plan.Plan{
Name: "Pro Plan",
Slug: "pro",
Features: []plan.Feature{
{
Key: "api_calls",
Type: plan.FeatureMetered,
Limit: 10000,
},
},
Pricing: &plan.Pricing{
BaseAmount: types.USD(4900), // $49.00
},
}
engine.CreatePlan(ctx, p)
// Create a subscription
sub := &subscription.Subscription{
TenantID: "tenant_123",
PlanID: p.ID,
Status: subscription.StatusActive,
}
engine.CreateSubscription(ctx, sub)
// Check entitlement (sub-millisecond)
ctx = context.WithValue(ctx, "tenant_id", "tenant_123")
result, _ := engine.Entitled(ctx, "api_calls")
if result.Allowed {
// Track usage (non-blocking)
engine.Meter(ctx, "api_calls", 1)
}
}