Getting Started
Install Ledger and create your first billing plan in under five minutes.
Prerequisites
- Go 1.22 or later
- A Go module (
go mod init) - PostgreSQL, SQLite, or Redis (for storage)
Install
go get github.com/xraph/ledgerStep 1: Create the billing engine
The Ledger engine is the central billing coordinator. It needs a store for persistence:
package main
import (
"context"
"log"
"time"
"github.com/xraph/ledger"
"github.com/xraph/ledger/store/postgres"
)
func main() {
ctx := context.Background()
// Create PostgreSQL store
store := postgres.New(pool) // your *pgxpool.Pool
// Create billing engine with configuration
engine := ledger.New(store,
ledger.WithMeterConfig(100, 5*time.Second), // Batch 100 events or 5 seconds
ledger.WithEntitlementCacheTTL(30*time.Second), // Cache entitlements for 30s
)
// Start the engine
if err := engine.Start(ctx); err != nil {
log.Fatal(err)
}
defer engine.Stop()
}Step 2: Set up multi-tenancy
Ledger extracts tenant ID and app ID from context for isolation:
// Set tenant context for all operations
ctx = context.WithValue(ctx, "tenant_id", "tenant_123")
ctx = context.WithValue(ctx, "app_id", "app_456")All operations are automatically scoped to the tenant — cross-tenant access is impossible.
Step 3: Create a pricing plan
Define what you're selling with features and pricing:
import (
"github.com/xraph/ledger/plan"
"github.com/xraph/ledger/types"
)
proPlan := &plan.Plan{
Name: "Pro Plan",
Slug: "pro",
Description: "Best for growing teams",
Features: []plan.Feature{
{
Key: "api_calls",
Type: plan.FeatureMetered,
Description: "API requests per month",
Limit: 10000,
},
{
Key: "seats",
Type: plan.FeatureLicensed,
Description: "Team member seats",
Limit: 5,
},
{
Key: "storage_gb",
Type: plan.FeatureMetered,
Description: "Storage in gigabytes",
Limit: 100,
},
},
Pricing: &plan.Pricing{
BaseAmount: types.USD(4900), // $49.00/month
Interval: plan.IntervalMonthly,
Tiers: []plan.PriceTier{
// First 1000 API calls free
{UpTo: 1000, UnitAmount: types.Zero()},
// Next 4000 at $0.01 each
{UpTo: 5000, UnitAmount: types.USD(1)},
// Everything else at $0.005 each
{UpTo: -1, UnitAmount: types.Cents(0.5)},
},
},
}
if err := engine.CreatePlan(ctx, proPlan); err != nil {
log.Fatal(err)
}Step 4: Create a subscription
Subscribe a customer to your plan:
import (
"github.com/xraph/ledger/subscription"
)
sub := &subscription.Subscription{
TenantID: "tenant_123",
CustomerID: "cust_abc123",
PlanID: proPlan.ID,
Status: subscription.StatusTrialing,
TrialEnd: time.Now().AddDate(0, 0, 14), // 14-day trial
CurrentPeriod: subscription.Period{
Start: time.Now(),
End: time.Now().AddDate(0, 1, 0),
},
}
if err := engine.CreateSubscription(ctx, sub); err != nil {
log.Fatal(err)
}Step 5: Check entitlements
Check if a customer can use a feature (sub-millisecond with cache):
// Check entitlement
result, err := engine.Entitled(ctx, "api_calls")
if err != nil {
log.Fatal(err)
}
if result.Allowed {
fmt.Printf("API calls remaining: %d/%d\n",
result.Remaining, result.Limit)
// Process the API request...
// Track usage (non-blocking, batched)
engine.Meter(ctx, "api_calls", 1)
} else {
// Return 429 Too Many Requests
fmt.Printf("Quota exceeded: %s\n", result.Reason)
}Step 6: Generate invoices
Generate invoices for billing periods:
// Generate invoice for current period
invoice, err := engine.GenerateInvoice(ctx, sub.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Invoice %s\n", invoice.ID)
fmt.Printf("Subtotal: %s\n", invoice.Subtotal.Format())
fmt.Printf("Tax: %s\n", invoice.Tax.Format())
fmt.Printf("Total: %s\n", invoice.Total.Format())
// Line items breakdown
for _, item := range invoice.LineItems {
fmt.Printf("- %s: %s x %d = %s\n",
item.Description,
item.UnitAmount.Format(),
item.Quantity,
item.Amount.Format())
}Step 7: Process payments
Integrate with payment providers:
import "github.com/xraph/ledger/provider/stripe"
// Configure Stripe provider
stripeProvider := stripe.New(stripeClient)
engine.SetProvider(stripeProvider)
// Collect payment
payment, err := engine.CollectPayment(ctx, invoice.ID,
provider.PaymentMethod{
Type: provider.MethodCard,
ID: "pm_card_visa", // Stripe payment method
})
if payment.Status == provider.StatusSucceeded {
fmt.Printf("Payment collected: %s\n", payment.Amount.Format())
}Complete example
Here's a complete billing flow:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/xraph/ledger"
"github.com/xraph/ledger/plan"
"github.com/xraph/ledger/store/memory"
"github.com/xraph/ledger/subscription"
"github.com/xraph/ledger/types"
)
func main() {
ctx := context.Background()
// Create engine with in-memory store for testing
engine := ledger.New(memory.New())
engine.Start(ctx)
defer engine.Stop()
// Set tenant context
ctx = context.WithValue(ctx, "tenant_id", "acme_corp")
// Create plan
plan := createStarterPlan()
engine.CreatePlan(ctx, plan)
// Create subscription
sub := createSubscription(plan.ID)
engine.CreateSubscription(ctx, sub)
// Simulate API usage
for i := 0; i < 100; i++ {
if result, _ := engine.Entitled(ctx, "api_calls"); result.Allowed {
engine.Meter(ctx, "api_calls", 1)
fmt.Printf("Request %d processed\n", i+1)
} else {
fmt.Printf("Quota exceeded at request %d\n", i+1)
break
}
}
// Generate invoice
invoice, _ := engine.GenerateInvoice(ctx, sub.ID)
fmt.Printf("\nInvoice Total: %s\n", invoice.Total.Format())
}