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 ¶
- Variables
- func BuiltinEventTypes() map[string]WebhookEventType
- type CancelInput
- type ChangePlanInput
- type CheckLimitsInput
- type CreatePlanInput
- type ListPlansPageInput
- type ListSubscriptionsPageInput
- type Manager
- func (m *Manager) Cancel(ctx context.Context, in CancelInput) (*Subscription, error)
- func (m *Manager) ChangePlan(ctx context.Context, in ChangePlanInput) (*Subscription, error)
- func (m *Manager) CheckLimit(ctx context.Context, subscriptionID, metric string, delta int64) error
- func (m *Manager) CheckLimits(ctx context.Context, in CheckLimitsInput) error
- func (m *Manager) Close()
- func (m *Manager) CreatePlan(ctx context.Context, in CreatePlanInput) (*Plan, error)
- func (m *Manager) DeletePlan(ctx context.Context, id string) error
- func (m *Manager) GetActiveSubscription(ctx context.Context, subscriberID string) (*Subscription, error)
- func (m *Manager) GetCurrentUsage(ctx context.Context, subscriptionID, metric string) (int64, error)
- func (m *Manager) GetPlan(ctx context.Context, id string) (*Plan, error)
- func (m *Manager) GetSubscription(ctx context.Context, id string) (*Subscription, error)
- func (m *Manager) GetUsageReport(ctx context.Context, subscriptionID string) ([]*UsageRecord, error)
- func (m *Manager) HandleWebhook(r *http.Request) error
- func (m *Manager) ListPlans(ctx context.Context, activeOnly bool) ([]*Plan, error)
- func (m *Manager) ListPlansPage(ctx context.Context, in ListPlansPageInput) (*Page[*Plan], error)
- func (m *Manager) ListSubscriptions(ctx context.Context, subscriberID string) ([]*Subscription, error)
- func (m *Manager) ListSubscriptionsPage(ctx context.Context, in ListSubscriptionsPageInput) (*Page[*Subscription], error)
- func (m *Manager) OnWebhookEvent(eventType WebhookEventType, h WebhookHandler)
- func (m *Manager) Pause(ctx context.Context, subscriptionID string) (*Subscription, error)
- func (m *Manager) RecordUsage(ctx context.Context, in RecordUsageInput) (*UsageRecord, error)
- func (m *Manager) Resume(ctx context.Context, subscriptionID string) (*Subscription, error)
- func (m *Manager) Subscribe(ctx context.Context, in SubscribeInput) (*Subscription, error)
- func (m *Manager) UpdatePlan(ctx context.Context, id string, in UpdatePlanInput) (*Plan, error)
- type Metrics
- type Options
- type Page
- type Plan
- type PlanInterval
- type RecordUsageInput
- type SubscribeInput
- type Subscription
- type SubscriptionStatus
- type UpdatePlanInput
- type UsageRecord
- type WebhookEvent
- type WebhookEventType
- type WebhookHandler
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
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 ¶
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 ¶
CreatePlan creates a new plan, persists it, and registers it with the payment provider (if one is configured).
func (*Manager) DeletePlan ¶
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 ¶
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 ¶
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 ¶
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 ¶
ListPlans returns all plans. Pass activeOnly = true to exclude archived plans.
func (*Manager) ListPlansPage ¶
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) 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) 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 ¶
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 ¶
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.
Source Files
¶
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. |