Ledger

Entities

The core data types in Ledger — Plans, Subscriptions, Invoices, Usage Events, and more.

All Ledger entities use TypeID identifiers for type-safe, sortable IDs. This page provides an overview of every entity and how they relate.

Base entity

type Entity struct {
    ID        string    `json:"id"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

Every entity struct embeds base timestamp tracking and a TypeID identifier.

Entity overview

Plan

The top-level pricing entity. Defines features, pricing tiers, and billing configurations for customers.

type Plan struct {
    Entity
    ID          PlanID   `json:"id"` // pln_...
    Name        string   `json:"name"`
    Description string   `json:"description"`
    Status      string   `json:"status"` // draft, active, archived

    // Billing configuration
    BillingPeriod   string `json:"billing_period"`   // month, year, etc
    Currency        string `json:"currency"`         // USD, EUR, etc
    TrialPeriodDays int    `json:"trial_period_days"`

    // Features and pricing
    Features     []Feature     `json:"features"`
    PricingTiers []PricingTier `json:"pricing_tiers"`

    // Metadata
    Metadata map[string]any `json:"metadata"`
}

See Plans for details.

Feature

A capability or resource included in a plan. Can be boolean (on/off) or metered (usage-based).

type Feature struct {
    ID          FeatureID `json:"id"` // fea_...
    PlanID      PlanID    `json:"plan_id"`
    Name        string    `json:"name"`
    Key         string    `json:"key"` // unique identifier for code
    Type        string    `json:"type"` // boolean, metered

    // For metered features
    MeterType   string `json:"meter_type,omitempty"`   // counter, gauge, timer
    Unit        string `json:"unit,omitempty"`         // api_calls, gb, minutes
    Aggregation string `json:"aggregation,omitempty"`  // sum, max, unique_count

    // Limits
    Limit       *int64 `json:"limit,omitempty"`        // max allowed usage
    Overage     bool   `json:"overage"`                // allow usage above limit

    Metadata map[string]any `json:"metadata"`
}

PricingTier

Defines the pricing structure for a plan — flat fee, per-seat, usage-based, or hybrid.

type PricingTier struct {
    ID         TierID  `json:"id"` // tier_...
    PlanID     PlanID  `json:"plan_id"`
    Name       string  `json:"name"`
    Type       string  `json:"type"` // flat, per_unit, tiered, volume

    // Flat pricing
    FlatAmount Money `json:"flat_amount,omitempty"`

    // Per-unit pricing
    UnitAmount Money  `json:"unit_amount,omitempty"`
    FeatureKey string `json:"feature_key,omitempty"` // which feature to meter

    // Tiered pricing brackets
    Brackets []PricingBracket `json:"brackets,omitempty"`

    Metadata map[string]any `json:"metadata"`
}

type PricingBracket struct {
    StartQuantity int64 `json:"start_quantity"`
    EndQuantity   *int64 `json:"end_quantity,omitempty"` // nil = unlimited
    UnitAmount    Money `json:"unit_amount"`
}

Subscription

A customer's active plan subscription with billing cycle and status tracking.

type Subscription struct {
    Entity
    ID         SubscriptionID `json:"id"` // sub_...
    CustomerID CustomerID     `json:"customer_id"`
    PlanID     PlanID         `json:"plan_id"`
    Status     string         `json:"status"` // trialing, active, past_due, canceled, expired

    // Billing cycle
    CurrentPeriodStart time.Time  `json:"current_period_start"`
    CurrentPeriodEnd   time.Time  `json:"current_period_end"`
    TrialEnd           *time.Time `json:"trial_end,omitempty"`

    // Cancellation
    CanceledAt       *time.Time `json:"canceled_at,omitempty"`
    CancelAtPeriodEnd bool      `json:"cancel_at_period_end"`

    // Payment
    PaymentMethodID string `json:"payment_method_id,omitempty"`

    // Entitlements (cached)
    Entitlements map[string]Entitlement `json:"entitlements"`

    Metadata map[string]any `json:"metadata"`
}

See Subscriptions for lifecycle details.

Customer

Represents a billable entity (person, organization, team).

type Customer struct {
    Entity
    ID    CustomerID `json:"id"` // cus_...
    Email string     `json:"email"`
    Name  string     `json:"name"`

    // Organization (for B2B)
    CompanyName string `json:"company_name,omitempty"`
    TaxID       string `json:"tax_id,omitempty"`

    // Billing details
    BillingAddress Address `json:"billing_address"`
    Currency       string  `json:"currency"` // default: USD

    // Payment provider
    StripeCustomerID string `json:"stripe_customer_id,omitempty"`
    PaddleCustomerID string `json:"paddle_customer_id,omitempty"`

    Metadata map[string]any `json:"metadata"`
}

Invoice

A billing document for a subscription period, including usage charges.

type Invoice struct {
    Entity
    ID             InvoiceID      `json:"id"` // inv_...
    CustomerID     CustomerID     `json:"customer_id"`
    SubscriptionID SubscriptionID `json:"subscription_id"`
    Status         string         `json:"status"` // draft, open, paid, void, uncollectible

    // Amounts (in cents)
    Subtotal      Money `json:"subtotal"`
    Tax           Money `json:"tax"`
    Total         Money `json:"total"`
    AmountPaid    Money `json:"amount_paid"`
    AmountDue     Money `json:"amount_due"`
    Currency      string `json:"currency"`

    // Line items
    Lines []InvoiceLine `json:"lines"`

    // Dates
    PeriodStart time.Time  `json:"period_start"`
    PeriodEnd   time.Time  `json:"period_end"`
    DueDate     time.Time  `json:"due_date"`
    PaidAt      *time.Time `json:"paid_at,omitempty"`

    // Payment
    PaymentIntentID string `json:"payment_intent_id,omitempty"`

    Metadata map[string]any `json:"metadata"`
}

type InvoiceLine struct {
    ID          string `json:"id"`
    Description string `json:"description"`
    Quantity    int64  `json:"quantity"`
    UnitAmount  Money  `json:"unit_amount"`
    Amount      Money  `json:"amount"`
    Type        string `json:"type"` // subscription, usage, one_time

    // For usage lines
    FeatureKey string     `json:"feature_key,omitempty"`
    PeriodStart time.Time `json:"period_start,omitempty"`
    PeriodEnd   time.Time `json:"period_end,omitempty"`
}

See Invoicing for generation and payment flow.

UsageEvent

A metered event submitted for billing calculation.

type UsageEvent struct {
    Entity
    ID         EventID    `json:"id"` // evt_...
    CustomerID CustomerID `json:"customer_id"`

    // Event identification
    EventName  string    `json:"event_name"` // e.g., "api_call", "storage_gb"
    EventTime  time.Time `json:"event_time"`
    IdempotencyKey string `json:"idempotency_key,omitempty"`

    // Value
    Value      float64           `json:"value"` // e.g., 1 for count, 2.5 for GB
    Unit       string            `json:"unit"`  // api_calls, gb, minutes

    // Context
    Properties map[string]any `json:"properties,omitempty"`

    // Processing
    Processed   bool       `json:"processed"`
    ProcessedAt *time.Time `json:"processed_at,omitempty"`
}

See Metering for high-throughput event ingestion.

Entitlement

Cached access rights for sub-millisecond checks.

type Entitlement struct {
    FeatureKey string `json:"feature_key"`
    Enabled    bool   `json:"enabled"`

    // For metered features
    Limit       *int64 `json:"limit,omitempty"`
    Usage       int64  `json:"usage"`
    Remaining   *int64 `json:"remaining,omitempty"`
    HasOverage  bool   `json:"has_overage"`
}

Entitlements are computed from subscription + plan + usage and cached for fast access checks.

Payment

Records a payment transaction.

type Payment struct {
    Entity
    ID             PaymentID  `json:"id"` // pay_...
    InvoiceID      InvoiceID  `json:"invoice_id"`
    CustomerID     CustomerID `json:"customer_id"`
    Amount         Money      `json:"amount"`
    Currency       string     `json:"currency"`
    Status         string     `json:"status"` // pending, succeeded, failed, refunded

    // Provider details
    ProviderID     string    `json:"provider_id"` // Stripe payment intent ID, etc
    Provider       string    `json:"provider"`    // stripe, paddle
    PaymentMethod  string    `json:"payment_method"` // card, bank_transfer, etc

    // Timestamps
    ProcessedAt *time.Time `json:"processed_at,omitempty"`
    FailedAt    *time.Time `json:"failed_at,omitempty"`
    FailureReason string   `json:"failure_reason,omitempty"`

    Metadata map[string]any `json:"metadata"`
}

Entity relationship diagram

Customer
  ├── Subscription[] ──→ Plan
  │         │              ├── Feature[]
  │         │              └── PricingTier[]
  │         │                    └── PricingBracket[]
  │         │
  │         ├── Entitlements (cached from Plan + Usage)
  │         └── Invoice[]
  │               ├── InvoiceLine[] (computed from Plan + Usage)
  │               └── Payment[]

  └── UsageEvent[] ──→ aggregated → Invoice Lines

TypeID prefixes

All entities use TypeIDs with consistent prefixes:

EntityPrefixExample
Planpln_pln_01h2xcejqtf2nbrexx3vqjhp41
Featurefea_fea_01h2xcejqtf2nbrexx3vqjhp42
Pricing Tiertier_tier_01h2xcejqtf2nbrexx3vqjhp43
Subscriptionsub_sub_01h2xcejqtf2nbrexx3vqjhp44
Customercus_cus_01h2xcejqtf2nbrexx3vqjhp45
Invoiceinv_inv_01h2xcejqtf2nbrexx3vqjhp46
Usage Eventevt_evt_01h2xcejqtf2nbrexx3vqjhp47
Paymentpay_pay_01h2xcejqtf2nbrexx3vqjhp48

TypeIDs are:

  • Type-safe — Prefix indicates entity type
  • Sortable — Encodes timestamp for chronological ordering
  • Globally unique — 128-bit UUID-like collision resistance
  • URL-safe — Base32 encoding without special characters

Money type

All monetary values use the Money type (integer cents) to avoid floating-point precision errors:

type Money int64 // Amount in smallest currency unit (cents)

// Examples:
amount := Money(1000) // $10.00
amount := Money(99)   // $0.99
amount := Money(12345) // $123.45

Helper functions:

money.FromDollars(10.50)  // → Money(1050)
money.ToDollars(Money(1050)) // → 10.50
money.Format(Money(1050), "USD") // → "$10.50"

Store interfaces

Each entity type defines its own store interface. These are composed into a single composite store:

type Store interface {
    PlanStore
    FeatureStore
    SubscriptionStore
    CustomerStore
    InvoiceStore
    UsageEventStore
    PaymentStore

    Migrate(ctx context.Context) error
    Ping(ctx context.Context) error
    Close() error
}

Example store methods:

type PlanStore interface {
    CreatePlan(ctx context.Context, plan *Plan) error
    GetPlan(ctx context.Context, id PlanID) (*Plan, error)
    GetPlanByName(ctx context.Context, name string) (*Plan, error)
    UpdatePlan(ctx context.Context, plan *Plan) error
    DeletePlan(ctx context.Context, id PlanID) error
    ListPlans(ctx context.Context, filters PlanFilters) ([]Plan, error)
}

On this page