subscriptions

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Dec 2, 2025 License: MIT Imports: 9 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound            = errors.New("subscription not found")
	ErrAlreadyExists       = errors.New("subscription already exists")
	ErrInvalidSubscription = errors.New("invalid subscription data")
)

Common errors returned by repository operations.

Functions

func CalculatePeriodEnd

func CalculatePeriodEnd(start time.Time, period BillingPeriod, interval int) time.Time

CalculatePeriodEnd calculates the end of a billing period given a start time.

Types

type BillingPeriod

type BillingPeriod string

BillingPeriod represents the unit of time for subscription billing.

const (
	PeriodDay   BillingPeriod = "day"
	PeriodWeek  BillingPeriod = "week"
	PeriodMonth BillingPeriod = "month"
	PeriodYear  BillingPeriod = "year"
)

type ChangeSubscriptionRequest

type ChangeSubscriptionRequest struct {
	SubscriptionID     string            // ID of existing subscription
	NewProductID       string            // New product/plan to switch to
	NewPriceID         string            // New Stripe price ID (for Stripe subscriptions)
	NewBillingPeriod   BillingPeriod     // New billing period
	NewBillingInterval int               // New billing interval
	ProrationBehavior  string            // "create_prorations", "none", "always_invoice"
	Metadata           map[string]string // Updated metadata
}

ChangeSubscriptionRequest contains parameters for upgrading or downgrading a subscription.

type ChangeSubscriptionResult

type ChangeSubscriptionResult struct {
	Subscription    Subscription
	PreviousProduct string
	NewProduct      string
	ProrationAmount int64 // Amount in cents (positive = charge, negative = credit)
	EffectiveDate   time.Time
}

ChangeSubscriptionResult contains the result of a plan change.

type CreateStripeSubscriptionRequest

type CreateStripeSubscriptionRequest struct {
	ProductID            string
	StripeCustomerID     string
	StripeSubscriptionID string
	BillingPeriod        BillingPeriod
	BillingInterval      int
	CurrentPeriodEnd     time.Time
	TrialEnd             *time.Time
	Metadata             map[string]string
}

CreateStripeSubscriptionRequest contains parameters for creating a Stripe subscription.

type CreateX402SubscriptionRequest

type CreateX402SubscriptionRequest struct {
	ProductID       string
	Wallet          string
	BillingPeriod   BillingPeriod
	BillingInterval int
	Metadata        map[string]string
}

CreateX402SubscriptionRequest contains parameters for creating an x402 subscription.

type MemoryRepository

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

MemoryRepository is an in-memory implementation of Repository for testing.

func NewMemoryRepository

func NewMemoryRepository() *MemoryRepository

NewMemoryRepository creates a new in-memory repository.

func (*MemoryRepository) Close

func (r *MemoryRepository) Close() error

Close implements Repository.Close (no-op for memory).

func (*MemoryRepository) Create

Create stores a new subscription.

func (*MemoryRepository) Delete

func (r *MemoryRepository) Delete(_ context.Context, id string) error

Delete removes a subscription.

func (*MemoryRepository) ExtendPeriod

func (r *MemoryRepository) ExtendPeriod(_ context.Context, id string, newStart, newEnd time.Time) error

ExtendPeriod updates the current period for renewals.

func (*MemoryRepository) Get

Get retrieves a subscription by ID.

func (*MemoryRepository) GetByStripeCustomerID

func (r *MemoryRepository) GetByStripeCustomerID(_ context.Context, customerID string) ([]Subscription, error)

GetByStripeCustomerID finds all subscriptions for a Stripe customer.

func (*MemoryRepository) GetByStripeSubscriptionID

func (r *MemoryRepository) GetByStripeSubscriptionID(_ context.Context, stripeSubID string) (Subscription, error)

GetByStripeSubscriptionID finds a subscription by Stripe subscription ID.

func (*MemoryRepository) GetByWallet

func (r *MemoryRepository) GetByWallet(_ context.Context, wallet, productID string) (Subscription, error)

GetByWallet finds an active subscription for a wallet and product.

func (*MemoryRepository) ListActive

func (r *MemoryRepository) ListActive(_ context.Context, productID string) ([]Subscription, error)

ListActive returns all active subscriptions.

func (*MemoryRepository) ListByProduct

func (r *MemoryRepository) ListByProduct(_ context.Context, productID string) ([]Subscription, error)

ListByProduct returns all subscriptions for a product.

func (*MemoryRepository) ListExpiring

func (r *MemoryRepository) ListExpiring(_ context.Context, before time.Time) ([]Subscription, error)

ListExpiring returns subscriptions expiring before the given time.

func (*MemoryRepository) Update

Update modifies an existing subscription.

func (*MemoryRepository) UpdateStatus

func (r *MemoryRepository) UpdateStatus(_ context.Context, id string, status Status) error

UpdateStatus changes a subscription's status.

type PaymentMethod

type PaymentMethod string

PaymentMethod indicates how the subscription is paid.

const (
	PaymentMethodStripe PaymentMethod = "stripe"
	PaymentMethodX402   PaymentMethod = "x402"
)

type PostgresRepository

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

PostgresRepository implements Repository using PostgreSQL.

func NewPostgresRepository

func NewPostgresRepository(connStr string) (*PostgresRepository, error)

NewPostgresRepository creates a new PostgreSQL repository.

func NewPostgresRepositoryWithDB

func NewPostgresRepositoryWithDB(db *sql.DB) *PostgresRepository

NewPostgresRepositoryWithDB creates a repository using a shared database connection.

func (*PostgresRepository) Close

func (r *PostgresRepository) Close() error

Close closes the database connection if owned.

func (*PostgresRepository) Create

func (r *PostgresRepository) Create(ctx context.Context, sub Subscription) error

Create stores a new subscription.

func (*PostgresRepository) Delete

func (r *PostgresRepository) Delete(ctx context.Context, id string) error

Delete removes a subscription.

func (*PostgresRepository) ExtendPeriod

func (r *PostgresRepository) ExtendPeriod(ctx context.Context, id string, newStart, newEnd time.Time) error

ExtendPeriod updates the current period for renewals.

func (*PostgresRepository) Get

Get retrieves a subscription by ID.

func (*PostgresRepository) GetByStripeCustomerID

func (r *PostgresRepository) GetByStripeCustomerID(ctx context.Context, customerID string) ([]Subscription, error)

GetByStripeCustomerID finds all subscriptions for a Stripe customer.

func (*PostgresRepository) GetByStripeSubscriptionID

func (r *PostgresRepository) GetByStripeSubscriptionID(ctx context.Context, stripeSubID string) (Subscription, error)

GetByStripeSubscriptionID finds a subscription by Stripe subscription ID.

func (*PostgresRepository) GetByWallet

func (r *PostgresRepository) GetByWallet(ctx context.Context, wallet, productID string) (Subscription, error)

GetByWallet finds an active subscription for a wallet and product.

func (*PostgresRepository) ListActive

func (r *PostgresRepository) ListActive(ctx context.Context, productID string) ([]Subscription, error)

ListActive returns all active subscriptions.

func (*PostgresRepository) ListByProduct

func (r *PostgresRepository) ListByProduct(ctx context.Context, productID string) ([]Subscription, error)

ListByProduct returns all subscriptions for a product.

func (*PostgresRepository) ListExpiring

func (r *PostgresRepository) ListExpiring(ctx context.Context, before time.Time) ([]Subscription, error)

ListExpiring returns subscriptions expiring before the given time.

func (*PostgresRepository) Update

func (r *PostgresRepository) Update(ctx context.Context, sub Subscription) error

Update modifies an existing subscription.

func (*PostgresRepository) UpdateStatus

func (r *PostgresRepository) UpdateStatus(ctx context.Context, id string, status Status) error

UpdateStatus changes a subscription's status.

func (*PostgresRepository) WithTableName

func (r *PostgresRepository) WithTableName(name string) *PostgresRepository

WithTableName returns a copy of the repository with a custom table name.

type ProductConfig

type ProductConfig struct {
	BillingPeriod    BillingPeriod `json:"billingPeriod" yaml:"billing_period"`
	BillingInterval  int           `json:"billingInterval" yaml:"billing_interval"`
	TrialDays        int           `json:"trialDays,omitempty" yaml:"trial_days"`
	StripePriceID    string        `json:"stripePriceId,omitempty" yaml:"stripe_price_id"`
	AllowX402        bool          `json:"allowX402" yaml:"allow_x402"`                // Allow x402 payment for this subscription
	GracePeriodHours int           `json:"gracePeriodHours" yaml:"grace_period_hours"` // Hours after expiry before blocking
}

ProductConfig defines subscription configuration for a product.

func (ProductConfig) IsValid

func (c ProductConfig) IsValid() bool

IsValid validates the product configuration.

type Repository

type Repository interface {
	// Create stores a new subscription.
	Create(ctx context.Context, sub Subscription) error

	// Get retrieves a subscription by ID.
	Get(ctx context.Context, id string) (Subscription, error)

	// Update modifies an existing subscription.
	Update(ctx context.Context, sub Subscription) error

	// Delete removes a subscription (soft delete by setting status to cancelled).
	Delete(ctx context.Context, id string) error

	// GetByWallet finds an active subscription for a wallet and product.
	GetByWallet(ctx context.Context, wallet, productID string) (Subscription, error)

	// GetByStripeSubscriptionID finds a subscription by Stripe subscription ID.
	GetByStripeSubscriptionID(ctx context.Context, stripeSubID string) (Subscription, error)

	// GetByStripeCustomerID finds all subscriptions for a Stripe customer.
	GetByStripeCustomerID(ctx context.Context, customerID string) ([]Subscription, error)

	// ListByProduct returns all subscriptions for a product.
	ListByProduct(ctx context.Context, productID string) ([]Subscription, error)

	// ListActive returns all active subscriptions, optionally filtered by product.
	ListActive(ctx context.Context, productID string) ([]Subscription, error)

	// ListExpiring returns subscriptions expiring before the given time.
	ListExpiring(ctx context.Context, before time.Time) ([]Subscription, error)

	// UpdateStatus changes a subscription's status.
	UpdateStatus(ctx context.Context, id string, status Status) error

	// ExtendPeriod updates the current period end time (for renewals).
	ExtendPeriod(ctx context.Context, id string, newStart, newEnd time.Time) error

	// Close releases any resources held by the repository.
	Close() error
}

Repository defines the interface for subscription storage.

func NewRepository

func NewRepository(cfg RepositoryConfig) (Repository, error)

NewRepository creates a repository based on configuration.

func NewRepositoryWithDB

func NewRepositoryWithDB(cfg RepositoryConfig, sharedDB *sql.DB) (Repository, error)

NewRepositoryWithDB creates a repository with an optional shared database connection.

type RepositoryConfig

type RepositoryConfig struct {
	Backend          string  // "memory" or "postgres"
	PostgresURL      string  // Connection string for postgres
	PostgresDB       *sql.DB // Optional shared database connection
	TableName        string  // Custom table name (default: "subscriptions")
	GracePeriodHours int     // Hours after expiry before blocking access
}

RepositoryConfig holds configuration for creating a repository.

type Service

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

Service provides subscription management operations.

func NewService

func NewService(repo Repository, gracePeriodHours int) *Service

NewService creates a new subscription service.

func (*Service) Cancel

func (s *Service) Cancel(ctx context.Context, id string, atPeriodEnd bool) error

Cancel cancels a subscription.

func (*Service) ChangeSubscription

func (s *Service) ChangeSubscription(ctx context.Context, req ChangeSubscriptionRequest) (*ChangeSubscriptionResult, error)

ChangeSubscription changes a subscription to a different plan (upgrade/downgrade). For Stripe subscriptions, this should be called after the Stripe API update. For x402 subscriptions, this handles the local database update.

func (*Service) CreateStripeSubscription

func (s *Service) CreateStripeSubscription(ctx context.Context, req CreateStripeSubscriptionRequest) (Subscription, error)

CreateStripeSubscription creates a new Stripe-backed subscription.

func (*Service) CreateX402Subscription

func (s *Service) CreateX402Subscription(ctx context.Context, req CreateX402SubscriptionRequest) (Subscription, error)

CreateX402Subscription creates a new x402 wallet-backed subscription.

func (*Service) ExpireOverdue

func (s *Service) ExpireOverdue(ctx context.Context) (int, error)

ExpireOverdue marks overdue subscriptions as expired.

func (*Service) ExtendX402Subscription

func (s *Service) ExtendX402Subscription(ctx context.Context, id string, period BillingPeriod, interval int) (Subscription, error)

ExtendX402Subscription extends an existing x402 subscription.

func (*Service) Get

func (s *Service) Get(ctx context.Context, id string) (Subscription, error)

Get retrieves a subscription by ID.

func (*Service) GetByStripeSubscriptionID

func (s *Service) GetByStripeSubscriptionID(ctx context.Context, stripeSubID string) (Subscription, error)

GetByStripeSubscriptionID retrieves a subscription by Stripe subscription ID.

func (*Service) GetByWallet

func (s *Service) GetByWallet(ctx context.Context, wallet, productID string) (Subscription, error)

GetByWallet retrieves a subscription by wallet and product.

func (*Service) HandleStripeCancelled

func (s *Service) HandleStripeCancelled(ctx context.Context, stripeSubID string) error

HandleStripeCancelled marks a subscription as cancelled.

func (*Service) HandleStripePaymentFailed

func (s *Service) HandleStripePaymentFailed(ctx context.Context, stripeSubID string) error

HandleStripePaymentFailed marks a subscription as past due.

func (*Service) HandleStripeRenewal

func (s *Service) HandleStripeRenewal(ctx context.Context, stripeSubID string, periodStart, periodEnd time.Time) error

HandleStripeRenewal processes a successful Stripe subscription renewal.

func (*Service) HandleStripeSubscriptionUpdated

func (s *Service) HandleStripeSubscriptionUpdated(ctx context.Context, stripeSubID string, update StripeSubscriptionUpdate) error

HandleStripeSubscriptionUpdated handles subscription update events from Stripe. This is more comprehensive than HandleStripeRenewal - it handles plan changes too.

func (*Service) HasAccess

func (s *Service) HasAccess(ctx context.Context, wallet, productID string) (bool, *Subscription, error)

HasAccess checks if a wallet has active subscription access to a product.

func (*Service) HasStripeAccess

func (s *Service) HasStripeAccess(ctx context.Context, stripeSubID string) (bool, *Subscription, error)

HasStripeAccess checks if a Stripe customer has active subscription access.

func (*Service) ListExpiring

func (s *Service) ListExpiring(ctx context.Context, within time.Duration) ([]Subscription, error)

ListExpiring returns subscriptions expiring within the given duration.

func (*Service) ReactivateSubscription

func (s *Service) ReactivateSubscription(ctx context.Context, id string) (Subscription, error)

ReactivateSubscription reactivates a cancelled subscription (if still within period).

type Status

type Status string

Status represents the current state of a subscription.

const (
	// StatusActive indicates the subscription is active and grants access.
	StatusActive Status = "active"

	// StatusPastDue indicates a Stripe payment failed but subscription not yet cancelled.
	StatusPastDue Status = "past_due"

	// StatusCancelled indicates the user cancelled the subscription.
	StatusCancelled Status = "cancelled"

	// StatusExpired indicates the subscription period ended without renewal.
	StatusExpired Status = "expired"

	// StatusTrialing indicates the subscription is in a free trial period.
	StatusTrialing Status = "trialing"
)

type StripeSubscriptionUpdate

type StripeSubscriptionUpdate struct {
	Status             Status
	CurrentPeriodStart time.Time
	CurrentPeriodEnd   time.Time
	CancelAtPeriodEnd  bool
	CancelledAt        *time.Time
	NewProductID       string        // Set if plan changed
	BillingPeriod      BillingPeriod // Set if billing interval changed
	BillingInterval    int
}

StripeSubscriptionUpdate contains fields that can be updated from Stripe webhooks.

type Subscription

type Subscription struct {
	ID        string `json:"id"`
	ProductID string `json:"productId"`

	// Subscriber identity (exactly one of these is set)
	Wallet               string `json:"wallet,omitempty"`               // x402: wallet address
	StripeCustomerID     string `json:"stripeCustomerId,omitempty"`     // Stripe: customer ID
	StripeSubscriptionID string `json:"stripeSubscriptionId,omitempty"` // Stripe: subscription ID

	// Billing configuration
	PaymentMethod   PaymentMethod `json:"paymentMethod"`
	BillingPeriod   BillingPeriod `json:"billingPeriod"`
	BillingInterval int           `json:"billingInterval"` // e.g., 1 month, 3 months

	// Status and dates
	Status             Status     `json:"status"`
	CurrentPeriodStart time.Time  `json:"currentPeriodStart"`
	CurrentPeriodEnd   time.Time  `json:"currentPeriodEnd"`
	TrialEnd           *time.Time `json:"trialEnd,omitempty"`
	CancelledAt        *time.Time `json:"cancelledAt,omitempty"`
	CancelAtPeriodEnd  bool       `json:"cancelAtPeriodEnd"` // Cancel at end of current period

	// Metadata
	Metadata  map[string]string `json:"metadata,omitempty"`
	CreatedAt time.Time         `json:"createdAt"`
	UpdatedAt time.Time         `json:"updatedAt"`
}

Subscription represents an active or historical subscription record.

func (Subscription) DaysUntilExpiration

func (s Subscription) DaysUntilExpiration() int

DaysUntilExpiration returns the number of days until the current period ends. Returns 0 if already expired or negative if past expiration.

func (Subscription) IsActive

func (s Subscription) IsActive() bool

IsActive checks if the subscription currently grants access.

func (Subscription) IsActiveAt

func (s Subscription) IsActiveAt(t time.Time) bool

IsActiveAt checks if the subscription grants access at the given time.

func (Subscription) IsTrialing

func (s Subscription) IsTrialing() bool

IsTrialing checks if the subscription is currently in a trial period.

func (Subscription) NextPeriodEnd

func (s Subscription) NextPeriodEnd() time.Time

NextPeriodEnd calculates what the next period end would be after renewal.

Jump to

Keyboard shortcuts

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