billow

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 26, 2026 License: MIT Imports: 14 Imported by: 0

README

billow

CI Go Reference Go Report Card Go Version

billow is a provider-agnostic subscription management library for Go backend services.

It handles plan management, subscriber lifecycle (subscribe / cancel / pause / resume / upgrade), metered usage tracking, and webhook dispatching — all behind clean interfaces so you can swap payment gateways without rewriting business logic.


Features

  • Plan CRUD — create, list, update, and archive subscription plans
  • Full subscription lifecycle — subscribe, cancel (immediately or at period-end), pause, resume, upgrade/downgrade
  • Metered usage tracking — record usage events, sum per billing period, enforce plan limits with CheckLimit / CheckLimits (batch)
  • Webhook dispatch — parse, normalise, and fan-out provider webhook events to your handlers
  • Async webhook dispatch — optional worker pool routes events by subscription ID, preserving per-subscription ordering
  • Persist-then-report usage — usage records are written locally first; a background sweeper reports them to the provider crash-safely
  • Cursor-based paginationListPlansPage / ListSubscriptionsPage with opaque cursors
  • Provider-agnostic — implement one interface for Stripe, Razorpay, or any other gateway
  • Pluggable persistence — ships with an in-memory store and a 256-shard high-throughput variant; bring your own Postgres / Mongo / DynamoDB adapter
  • Instrumentation hooks — optional Metrics interface for Prometheus, Datadog, or any custom backend
  • Zero external dependencies — only the Go standard library

Installation

go get github.com/nulllvoid/billow

Requires Go 1.21 or later.


Quick start

package main

import (
    "context"
    "log"
    "net/http"

    billow "github.com/nulllvoid/billow"
    "github.com/nulllvoid/billow/store/memory"
)

func main() {
    mgr := billow.NewManager(billow.Options{
        Provider: myStripeProvider, // implements provider.PaymentProvider
        Plans:    memory.NewPlanStore(),
        Subs:     memory.NewSubscriptionStore(),
        Usage:    memory.NewUsageStore(),
    })
    defer mgr.Close() // drains background goroutines on shutdown

    // Register webhook handlers (safe to call at any time, from any goroutine).
    mgr.OnWebhookEvent(billow.EventPaymentFailed, func(e *billow.WebhookEvent) error {
        log.Printf("payment failed for %s", e.Subscription.SubscriberID)
        return nil
    })

    ctx := context.Background()

    // Create a plan.
    pro, _ := mgr.CreatePlan(ctx, billow.CreatePlanInput{
        Name:      "Pro",
        Amount:    2900, // $29.00 in cents
        Currency:  "usd",
        Interval:  billow.PlanIntervalMonth,
        TrialDays: 14,
        Limits:    map[string]int64{"api_calls": 10_000},
    })

    // Subscribe a user.
    sub, _ := mgr.Subscribe(ctx, billow.SubscribeInput{
        SubscriberID: "user_42",
        PlanID:       pro.ID,
    })

    // Record usage and check a single limit.
    mgr.RecordUsage(ctx, billow.RecordUsageInput{
        SubscriptionID: sub.ID,
        Metric:         "api_calls",
        Quantity:       250,
    })
    if err := mgr.CheckLimit(ctx, sub.ID, "api_calls", 1); err != nil {
        log.Println("limit exceeded:", err)
    }

    // Check multiple limits in one call (single subscription + plan fetch).
    if err := mgr.CheckLimits(ctx, billow.CheckLimitsInput{
        SubscriptionID: sub.ID,
        Deltas:         map[string]int64{"api_calls": 100, "seats": 1},
    }); err != nil {
        log.Println("limit exceeded:", err)
    }

    // Mount the webhook endpoint.
    http.HandleFunc("/webhooks/payments", func(w http.ResponseWriter, r *http.Request) {
        if err := mgr.HandleWebhook(r); err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        w.WriteHeader(http.StatusNoContent)
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}

See examples/basic/ for a fully working demo using a mock provider and in-memory stores.


Architecture

billow.Manager               ← main entry point
├── provider.PaymentProvider ← implement once per payment gateway
├── store.PlanStore          ← persist plans
├── store.SubscriptionStore  ← persist subscriptions
├── store.UsageStore         ← persist metered usage records
├── dispatchPool             ← optional async webhook worker pool
├── usageReporter            ← optional persist-then-report sweeper
└── planCache                ← in-process TTL cache for hot CheckLimit path

store/memory/
├── PlanStore                ← simple mutex-guarded map
├── SubscriptionStore        ← RWMutex + secondary indexes (O(1) lookups)
├── ShardedSubscriptionStore ← 256-shard variant for high write throughput
└── UsageStore               ← nested [subID][metric] index (O(k) SumUsage)

examples/basic/              ← end-to-end demo

Options reference

billow.NewManager(billow.Options{
    // Required
    Provider: myProvider,          // payment gateway adapter
    Plans:    memory.NewPlanStore(),
    Subs:     memory.NewSubscriptionStore(),
    Usage:    memory.NewUsageStore(),

    // ID generation (default: time-ordered UUID v7 via crypto/rand)
    IDGenerator: func() string { ... },

    // Webhook event mapping (default: built-in Stripe + Razorpay map)
    // Set to replace built-ins entirely; call BuiltinEventTypes() to extend them.
    EventTypeMapper: func(providerType string) billow.WebhookEventType { ... },

    // Plan cache (default: 5 min TTL; set negative to disable)
    PlanCacheTTL: 10 * time.Minute,

    // Async webhook dispatch (default: 0 = synchronous)
    DispatchWorkers:    16,  // goroutines; events for same sub always on same worker
    DispatchQueueDepth: 256, // per-worker channel buffer

    // Persist-then-report usage sweeper
    // Active only when Usage implements store.ReportableUsageStore and Provider is set.
    UsageReportInterval: 30 * time.Second,
    UsageReportBatch:    100,

    // Instrumentation (default: no-op)
    Metrics: myPrometheusMetrics, // implements billow.Metrics
})

Implementing a payment provider

Implement provider.PaymentProvider and pass it to NewManager:

type PaymentProvider interface {
    CreatePlan(ctx, plan) (providerID string, err error)
    UpdatePlan(ctx, plan) error
    DeletePlan(ctx, providerPlanID) error

    CreateSubscription(ctx, sub, plan) (providerID string, err error)
    CancelSubscription(ctx, providerSubID, immediately) error
    PauseSubscription(ctx, providerSubID) error
    ResumeSubscription(ctx, providerSubID) error
    ChangeSubscriptionPlan(ctx, providerSubID, newPlan) error

    ParseWebhook(r *http.Request) (*WebhookEvent, error)
    ReportUsage(ctx, providerSubID, metric, quantity) error
}

The built-in webhook handler maps common Stripe and Razorpay event names to canonical WebhookEventType constants. Unknown event names pass through as-is. Call BuiltinEventTypes() to get the default map and extend or replace it via Options.EventTypeMapper.


Webhook events

Constant Stripe event Razorpay event
EventSubscriptionCreated customer.subscription.created subscription.activated
EventSubscriptionUpdated customer.subscription.updated
EventSubscriptionCanceled customer.subscription.deleted subscription.cancelled
EventSubscriptionPaused customer.subscription.paused subscription.paused
EventSubscriptionResumed customer.subscription.resumed subscription.resumed
EventSubscriptionRenewed
EventPaymentSucceeded invoice.payment_succeeded subscription.charged
EventPaymentFailed invoice.payment_failed
EventTrialEnding customer.subscription.trial_will_end
EventTrialEnded

Persistence store interfaces

billow ships with two in-memory SubscriptionStore implementations:

Type Use case
memory.NewSubscriptionStore() Development, testing, single-instance deployments
memory.NewShardedSubscriptionStore() High-throughput scenarios; 256 independent shards eliminate single-mutex contention

Both implement store.AtomicSubscriptionStore, enabling the TOCTOU-safe subscribe path automatically.

For production, implement store.PlanStore, store.SubscriptionStore, and store.UsageStore against your database. To enable crash-safe provider usage reporting, also implement store.ReportableUsageStore.


Pagination

// First page
page, _ := mgr.ListPlansPage(ctx, billow.ListPlansPageInput{
    ActiveOnly: true,
    Limit:      20,
})
for _, p := range page.Items { ... }

// Next page
if page.NextCursor != "" {
    page2, _ := mgr.ListPlansPage(ctx, billow.ListPlansPageInput{
        ActiveOnly: true,
        Limit:      20,
        Cursor:     page.NextCursor,
    })
}

ListSubscriptionsPage works identically and supports SubscriberID, PlanID, and Status filters.


Instrumentation

Implement billow.Metrics and pass it via Options.Metrics:

type Metrics interface {
    DispatchDuration(eventType string, success bool, d time.Duration)
    UsageReportDuration(metric string, success bool, d time.Duration)
    PlanCacheHit(hit bool)
    WorkerQueueDepth(workerIndex int, depth int)
}

When nil, all calls are no-ops with zero allocations.


Graceful shutdown

// Call Close once at application shutdown.
// Waits for the dispatch pool to drain and the usage reporter to flush.
mgr.Close()

Running tests

go test -race ./...

Contributing

Contributions are welcome! Please read CONTRIBUTING.md before opening a pull request.


License

billow is released under the MIT License.

Documentation

Overview

Package billow provides a provider-agnostic subscription engine for Go backend services.

Quick start:

mgr := billow.NewManager(billow.Options{
    Provider: myStripeProvider,  // implements provider.PaymentProvider
    Plans:    memory.NewPlanStore(),
    Subs:     memory.NewSubscriptionStore(),
    Usage:    memory.NewUsageStore(),
})

// Register webhook event handlers (goroutine-safe; may be called at any time)
mgr.OnWebhookEvent(billow.EventPaymentFailed, func(e *billow.WebhookEvent) error {
    // send email, suspend access, etc.
    return nil
})

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrPlanNotFound         = errors.New("subscriptions: plan not found")
	ErrSubscriptionNotFound = errors.New("subscriptions: subscription not found")
	ErrAlreadySubscribed    = errors.New("subscriptions: subscriber already has an active subscription")
	ErrNotSubscribed        = errors.New("subscriptions: subscriber has no active subscription")
	ErrUsageLimitExceeded   = errors.New("subscriptions: usage limit exceeded for metric")
	ErrProviderNotSet       = errors.New("subscriptions: payment provider not configured")
	ErrInvalidWebhook       = errors.New("subscriptions: invalid or unverified webhook payload")
)

Functions

func BuiltinEventTypes

func BuiltinEventTypes() map[string]WebhookEventType

BuiltinEventTypes returns a fresh copy of the default Stripe and Razorpay event-name → WebhookEventType mappings. Use this as a starting point when constructing a custom Options.EventTypeMapper:

m := billow.BuiltinEventTypes()
m["paddle.subscription.created"] = billow.EventSubscriptionCreated
mgr := billow.NewManager(billow.Options{
    EventTypeMapper: func(t string) billow.WebhookEventType {
        if v, ok := m[t]; ok { return v }
        return billow.WebhookEventType(t)
    },
})

Types

type CancelInput

type CancelInput struct {
	SubscriptionID string
	Immediately    bool // false = cancel at end of current period
}

CancelInput is the input for cancelling a subscription.

type ChangePlanInput

type ChangePlanInput struct {
	SubscriptionID string
	NewPlanID      string
}

ChangePlanInput is the input for upgrading or downgrading a subscription.

type CheckLimitsInput

type CheckLimitsInput struct {
	SubscriptionID string
	// Deltas maps metric name to the quantity about to be consumed.
	// Metrics absent from the plan's Limits map (or with limit 0) are skipped.
	Deltas map[string]int64
}

CheckLimitsInput is the input for a batch limit check across multiple metrics.

type CreatePlanInput

type CreatePlanInput struct {
	Name          string
	Description   string
	Amount        int64 // smallest currency unit
	Currency      string
	Interval      PlanInterval
	IntervalCount int // defaults to 1
	TrialDays     int
	Features      []string
	Limits        map[string]int64 // metric → limit (0 = unlimited)
	Metadata      map[string]string
}

CreatePlanInput is the input for creating a new subscription plan.

type ListPlansPageInput

type ListPlansPageInput struct {
	ActiveOnly bool
	// Cursor is the opaque token returned by a previous call.
	// Pass empty string to start from the beginning.
	Cursor string
	// Limit is the maximum number of plans to return. Defaults to 20.
	Limit int
}

ListPlansPageInput is the input for a paginated plan list.

type ListSubscriptionsPageInput

type ListSubscriptionsPageInput struct {
	SubscriberID string
	PlanID       string
	Status       string
	Cursor       string
	Limit        int
}

ListSubscriptionsPageInput is the input for a paginated subscription list.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager is the central object backend developers interact with. Create one at startup and keep it for the lifetime of the application. Call Close when the application shuts down to drain in-flight work.

func NewManager

func NewManager(opts Options) *Manager

NewManager creates a Manager from the given options. It panics when required fields are missing so the mistake is caught at startup, not at the first API call.

func (*Manager) Cancel

func (m *Manager) Cancel(ctx context.Context, in CancelInput) (*Subscription, error)

Cancel cancels a subscription.

func (*Manager) ChangePlan

func (m *Manager) ChangePlan(ctx context.Context, in ChangePlanInput) (*Subscription, error)

ChangePlan upgrades or downgrades a subscription to a different plan.

func (*Manager) CheckLimit

func (m *Manager) CheckLimit(ctx context.Context, subscriptionID, metric string, delta int64) error

CheckLimit verifies whether adding delta units of metric would exceed the plan's configured limit for that metric. Returns ErrUsageLimitExceeded when the limit would be breached. Plans with a limit of 0 are treated as unlimited.

func (*Manager) CheckLimits

func (m *Manager) CheckLimits(ctx context.Context, in CheckLimitsInput) error

CheckLimits checks all supplied metrics in a single store round-trip. It fetches the subscription and plan once, then fans out SumUsage calls for only the metrics that have a non-zero limit on the plan. Returns the first ErrUsageLimitExceeded encountered, or nil if all pass.

func (*Manager) Close

func (m *Manager) Close()

Close drains all background goroutines and waits for them to finish. It should be called once when the application shuts down. After Close returns, no further background work will be performed.

func (*Manager) CreatePlan

func (m *Manager) CreatePlan(ctx context.Context, in CreatePlanInput) (*Plan, error)

CreatePlan creates a new plan, persists it, and registers it with the payment provider (if one is configured).

func (*Manager) DeletePlan

func (m *Manager) DeletePlan(ctx context.Context, id string) error

DeletePlan deactivates and removes a plan. Existing subscriptions on the plan are not affected.

func (*Manager) GetActiveSubscription

func (m *Manager) GetActiveSubscription(ctx context.Context, subscriberID string) (*Subscription, error)

GetActiveSubscription returns the active/trialing subscription for a subscriber.

func (*Manager) GetCurrentUsage

func (m *Manager) GetCurrentUsage(ctx context.Context, subscriptionID, metric string) (int64, error)

GetCurrentUsage returns the total usage for a metric in the subscription's current billing period.

func (*Manager) GetPlan

func (m *Manager) GetPlan(ctx context.Context, id string) (*Plan, error)

GetPlan returns a plan by its internal ID. Served from the in-process cache when available; the cache is always consistent with the store for plans mutated through this Manager.

func (*Manager) GetSubscription

func (m *Manager) GetSubscription(ctx context.Context, id string) (*Subscription, error)

GetSubscription returns a subscription by its internal ID.

func (*Manager) GetUsageReport

func (m *Manager) GetUsageReport(ctx context.Context, subscriptionID string) ([]*UsageRecord, error)

GetUsageReport returns all usage records for the current billing period.

func (*Manager) HandleWebhook

func (m *Manager) HandleWebhook(r *http.Request) error

HandleWebhook validates and processes an incoming webhook from the payment provider. It looks up the affected subscription, updates its state, and dispatches registered event handlers.

Mount this on your HTTP router:

http.HandleFunc("/webhooks/payments", func(w http.ResponseWriter, r *http.Request) {
    if err := mgr.HandleWebhook(r); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    w.WriteHeader(http.StatusNoContent)
})

func (*Manager) ListPlans

func (m *Manager) ListPlans(ctx context.Context, activeOnly bool) ([]*Plan, error)

ListPlans returns all plans. Pass activeOnly = true to exclude archived plans.

func (*Manager) ListPlansPage

func (m *Manager) ListPlansPage(ctx context.Context, in ListPlansPageInput) (*Page[*Plan], error)

ListPlansPage returns a single page of plans sorted lexicographically by ID. Pass the returned Page.NextCursor as the Cursor field on the next call to retrieve subsequent pages.

func (*Manager) ListSubscriptions

func (m *Manager) ListSubscriptions(ctx context.Context, subscriberID string) ([]*Subscription, error)

ListSubscriptions lists subscriptions for a subscriber.

func (*Manager) ListSubscriptionsPage

func (m *Manager) ListSubscriptionsPage(ctx context.Context, in ListSubscriptionsPageInput) (*Page[*Subscription], error)

ListSubscriptionsPage returns a single page of subscriptions sorted by ID.

func (*Manager) OnWebhookEvent

func (m *Manager) OnWebhookEvent(eventType WebhookEventType, h WebhookHandler)

OnWebhookEvent registers a handler for a specific webhook event type. Multiple handlers can be registered for the same event; they are called in registration order. Safe to call concurrently at any time, including after the Manager has started serving traffic.

func (*Manager) Pause

func (m *Manager) Pause(ctx context.Context, subscriptionID string) (*Subscription, error)

Pause pauses billing on a subscription.

func (*Manager) RecordUsage

func (m *Manager) RecordUsage(ctx context.Context, in RecordUsageInput) (*UsageRecord, error)

RecordUsage appends a usage record for the given subscription. If in.ReportToProvider is true and a provider is configured, the usage is also reported to the provider for metered billing.

func (*Manager) Resume

func (m *Manager) Resume(ctx context.Context, subscriptionID string) (*Subscription, error)

Resume resumes a paused subscription.

func (*Manager) Subscribe

func (m *Manager) Subscribe(ctx context.Context, in SubscribeInput) (*Subscription, error)

Subscribe creates a new subscription for a subscriber on the given plan. Returns ErrAlreadySubscribed if the subscriber already has an active or trialing subscription.

When the configured store implements store.AtomicSubscriptionStore, the duplicate-subscriber check and the insert are performed atomically, eliminating the TOCTOU race. Otherwise a best-effort two-step check is used (safe for single-instance deployments).

func (*Manager) UpdatePlan

func (m *Manager) UpdatePlan(ctx context.Context, id string, in UpdatePlanInput) (*Plan, error)

UpdatePlan applies patch-style updates to an existing plan and syncs the mutable fields with the payment provider.

type Metrics

type Metrics interface {
	// DispatchDuration records how long a single webhook dispatch took,
	// along with the canonical event type and whether it succeeded.
	DispatchDuration(eventType string, success bool, d time.Duration)

	// UsageReportDuration records the duration of a single provider
	// usage-report attempt, along with the metric name and success flag.
	UsageReportDuration(metric string, success bool, d time.Duration)

	// PlanCacheHit records a plan-cache lookup outcome (hit=true / miss=false).
	PlanCacheHit(hit bool)

	// WorkerQueueDepth records the current depth of a dispatch worker queue.
	// workerIndex identifies which worker's queue is being sampled.
	WorkerQueueDepth(workerIndex int, depth int)
}

Metrics is an optional hook interface that lets callers plug in a Prometheus, Datadog, or any custom instrumentation backend. Pass a Metrics implementation via Options.Metrics; a nil value (the default) silently disables all instrumentation.

All methods must be safe for concurrent use and must not block.

type Options

type Options struct {
	// Provider is the payment gateway adapter. Required.
	Provider provider.PaymentProvider

	// Plans, Subs, and Usage are the persistence stores.
	// Use store/memory for development; supply your own for production.
	Plans store.PlanStore
	Subs  store.SubscriptionStore
	Usage store.UsageStore

	// IDGenerator is called to create new IDs for plans, subscriptions, and
	// usage records. Defaults to a UUID v7 generator backed by crypto/rand;
	// safe across multiple processes and instances.
	IDGenerator func() string

	// EventTypeMapper translates provider-native event names to canonical
	// WebhookEventType values. When set it replaces the built-in Stripe/Razorpay
	// mappings entirely — call BuiltinEventTypes() to start from the defaults
	// and extend them. When nil the built-in mappings are used.
	EventTypeMapper func(providerType string) WebhookEventType

	// PlanCacheTTL controls how long fetched plans are kept in the in-process
	// cache. Plans are admin-only resources that rarely change; caching them
	// eliminates a store round-trip on every CheckLimit / CheckLimits call.
	// Defaults to 5 minutes when zero. Set to a negative value to disable.
	PlanCacheTTL time.Duration

	// DispatchWorkers is the number of async webhook-dispatch goroutines.
	// When > 0, HandleWebhook enqueues the event and returns immediately;
	// handlers run in the background. When 0 (default), dispatch is synchronous.
	DispatchWorkers int

	// DispatchQueueDepth is the per-worker channel buffer size. Defaults to 256.
	// Only meaningful when DispatchWorkers > 0.
	DispatchQueueDepth int

	// UsageReportInterval is how often the persist-then-report sweeper runs.
	// Defaults to 30 s. Only active when Usage implements store.ReportableUsageStore
	// and a Provider is set.
	UsageReportInterval time.Duration

	// UsageReportBatch is the maximum number of records flushed per sweep.
	// Defaults to 100.
	UsageReportBatch int

	// Metrics is an optional instrumentation backend. When nil all metrics
	// calls are no-ops.
	Metrics Metrics
}

Options configures a Manager.

type Page

type Page[T any] struct {
	Items      []T
	NextCursor string // empty when this is the last page
}

Page is a generic paginated result set returned by list methods that support cursor-based pagination.

type Plan

type Plan struct {
	ID            string // internal ID
	ProviderID    string // payment provider's plan/price ID
	Name          string
	Description   string
	Amount        int64  // smallest currency unit (cents, paise, etc.)
	Currency      string // ISO 4217 (e.g. "usd", "inr")
	Interval      PlanInterval
	IntervalCount int // e.g. 3 + Month = billed every 3 months
	TrialDays     int
	Features      []string         // human-readable feature list
	Limits        map[string]int64 // metric name → limit (0 = unlimited)
	Metadata      map[string]string
	Active        bool
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

Plan defines a subscription tier (e.g. Free, Pro, Enterprise).

type PlanInterval

type PlanInterval string

PlanInterval represents the billing interval for a plan.

const (
	PlanIntervalDay   PlanInterval = "day"
	PlanIntervalWeek  PlanInterval = "week"
	PlanIntervalMonth PlanInterval = "month"
	PlanIntervalYear  PlanInterval = "year"
)

type RecordUsageInput

type RecordUsageInput struct {
	SubscriptionID string
	Metric         string // e.g. "api_calls", "seats", "gb_storage"
	Quantity       int64
	RecordedAt     time.Time // defaults to now
	Metadata       map[string]string
	// ReportToProvider controls whether the usage is also pushed to the payment
	// provider (for metered billing). Defaults to false.
	ReportToProvider bool
}

RecordUsageInput is the input for recording a metered usage event.

type SubscribeInput

type SubscribeInput struct {
	SubscriberID string // your user/tenant ID
	PlanID       string
	Metadata     map[string]string
}

SubscribeInput is the input for creating a new subscription.

type Subscription

type Subscription struct {
	ID                 string
	ProviderID         string // payment provider's subscription ID
	SubscriberID       string // your user/tenant/org ID
	PlanID             string
	Status             SubscriptionStatus
	CurrentPeriodStart time.Time
	CurrentPeriodEnd   time.Time
	TrialStart         *time.Time
	TrialEnd           *time.Time
	CanceledAt         *time.Time
	PausedAt           *time.Time
	Metadata           map[string]string
	CreatedAt          time.Time
	UpdatedAt          time.Time
}

Subscription is an active (or historical) subscription for a subscriber.

func (*Subscription) IsActive

func (s *Subscription) IsActive() bool

IsActive returns true when the subscription allows feature access.

type SubscriptionStatus

type SubscriptionStatus string

SubscriptionStatus represents the lifecycle state of a subscription.

const (
	StatusTrialing SubscriptionStatus = "trialing"
	StatusActive   SubscriptionStatus = "active"
	StatusPaused   SubscriptionStatus = "paused"
	StatusPastDue  SubscriptionStatus = "past_due"
	StatusCanceled SubscriptionStatus = "canceled"
	StatusExpired  SubscriptionStatus = "expired"
)

type UpdatePlanInput

type UpdatePlanInput struct {
	Name        *string
	Description *string
	Features    []string
	Limits      map[string]int64
	Metadata    map[string]string
	Active      *bool
}

UpdatePlanInput holds the fields that can be updated after creation. Only non-zero / non-nil fields are applied (patch semantics).

type UsageRecord

type UsageRecord struct {
	ID             string
	SubscriptionID string
	Metric         string // e.g. "api_calls", "seats", "gb_storage"
	Quantity       int64
	RecordedAt     time.Time
	Metadata       map[string]string
	// ProviderReportedAt is non-nil once this record has been successfully
	// pushed to the payment provider. Nil means the report is still pending.
	ProviderReportedAt *time.Time
}

UsageRecord tracks a single metered usage event.

type WebhookEvent

type WebhookEvent struct {
	ID            string // provider's event ID
	Type          WebhookEventType
	ProviderSubID string         // provider's subscription ID
	Subscription  *Subscription  // nil when not yet matched
	Data          map[string]any // raw provider payload (for custom handling)
	OccurredAt    time.Time
}

WebhookEvent is the normalised event the Manager passes to your handlers.

type WebhookEventType

type WebhookEventType string

WebhookEventType is the canonical event name emitted by the Manager.

const (
	EventSubscriptionCreated  WebhookEventType = "subscription.created"
	EventSubscriptionUpdated  WebhookEventType = "subscription.updated"
	EventSubscriptionCanceled WebhookEventType = "subscription.canceled"
	EventSubscriptionPaused   WebhookEventType = "subscription.paused"
	EventSubscriptionResumed  WebhookEventType = "subscription.resumed"
	EventSubscriptionRenewed  WebhookEventType = "subscription.renewed"
	EventPaymentSucceeded     WebhookEventType = "payment.succeeded"
	EventPaymentFailed        WebhookEventType = "payment.failed"
	EventTrialEnding          WebhookEventType = "trial.ending"
	EventTrialEnded           WebhookEventType = "trial.ended"
)

type WebhookHandler

type WebhookHandler func(event *WebhookEvent) error

WebhookHandler is a callback registered for a specific event type.

Directories

Path Synopsis
examples
basic command
Package main demonstrates how to wire up and use the subscriptions package without any real payment provider (using a mock provider and the in-memory store).
Package main demonstrates how to wire up and use the subscriptions package without any real payment provider (using a mock provider and the in-memory store).
Package provider defines the PaymentProvider interface.
Package provider defines the PaymentProvider interface.
Package store defines the persistence interfaces used by the Manager.
Package store defines the persistence interfaces used by the Manager.
memory
Package memory provides a thread-safe in-memory implementation of the store.PlanStore, store.SubscriptionStore, and store.UsageStore interfaces.
Package memory provides a thread-safe in-memory implementation of the store.PlanStore, store.SubscriptionStore, and store.UsageStore interfaces.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL