Ledger

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/ledger

Step 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())
}

Next steps

On this page