Documentation
¶
Index ¶
- Variables
- func CalculatePeriodEnd(start time.Time, period BillingPeriod, interval int) time.Time
- type BillingPeriod
- type ChangeSubscriptionRequest
- type ChangeSubscriptionResult
- type CreateStripeSubscriptionRequest
- type CreateX402SubscriptionRequest
- type MemoryRepository
- func (r *MemoryRepository) Close() error
- func (r *MemoryRepository) Create(_ context.Context, sub Subscription) error
- func (r *MemoryRepository) Delete(_ context.Context, id string) error
- func (r *MemoryRepository) ExtendPeriod(_ context.Context, id string, newStart, newEnd time.Time) error
- func (r *MemoryRepository) Get(_ context.Context, id string) (Subscription, error)
- func (r *MemoryRepository) GetByStripeCustomerID(_ context.Context, customerID string) ([]Subscription, error)
- func (r *MemoryRepository) GetByStripeSubscriptionID(_ context.Context, stripeSubID string) (Subscription, error)
- func (r *MemoryRepository) GetByWallet(_ context.Context, wallet, productID string) (Subscription, error)
- func (r *MemoryRepository) ListActive(_ context.Context, productID string) ([]Subscription, error)
- func (r *MemoryRepository) ListByProduct(_ context.Context, productID string) ([]Subscription, error)
- func (r *MemoryRepository) ListExpiring(_ context.Context, before time.Time) ([]Subscription, error)
- func (r *MemoryRepository) Update(_ context.Context, sub Subscription) error
- func (r *MemoryRepository) UpdateStatus(_ context.Context, id string, status Status) error
- type PaymentMethod
- type PostgresRepository
- func (r *PostgresRepository) Close() error
- func (r *PostgresRepository) Create(ctx context.Context, sub Subscription) error
- func (r *PostgresRepository) Delete(ctx context.Context, id string) error
- func (r *PostgresRepository) ExtendPeriod(ctx context.Context, id string, newStart, newEnd time.Time) error
- func (r *PostgresRepository) Get(ctx context.Context, id string) (Subscription, error)
- func (r *PostgresRepository) GetByStripeCustomerID(ctx context.Context, customerID string) ([]Subscription, error)
- func (r *PostgresRepository) GetByStripeSubscriptionID(ctx context.Context, stripeSubID string) (Subscription, error)
- func (r *PostgresRepository) GetByWallet(ctx context.Context, wallet, productID string) (Subscription, error)
- func (r *PostgresRepository) ListActive(ctx context.Context, productID string) ([]Subscription, error)
- func (r *PostgresRepository) ListByProduct(ctx context.Context, productID string) ([]Subscription, error)
- func (r *PostgresRepository) ListExpiring(ctx context.Context, before time.Time) ([]Subscription, error)
- func (r *PostgresRepository) Update(ctx context.Context, sub Subscription) error
- func (r *PostgresRepository) UpdateStatus(ctx context.Context, id string, status Status) error
- func (r *PostgresRepository) WithTableName(name string) *PostgresRepository
- type ProductConfig
- type Repository
- type RepositoryConfig
- type Service
- func (s *Service) Cancel(ctx context.Context, id string, atPeriodEnd bool) error
- func (s *Service) ChangeSubscription(ctx context.Context, req ChangeSubscriptionRequest) (*ChangeSubscriptionResult, error)
- func (s *Service) CreateStripeSubscription(ctx context.Context, req CreateStripeSubscriptionRequest) (Subscription, error)
- func (s *Service) CreateX402Subscription(ctx context.Context, req CreateX402SubscriptionRequest) (Subscription, error)
- func (s *Service) ExpireOverdue(ctx context.Context) (int, error)
- func (s *Service) ExtendX402Subscription(ctx context.Context, id string, period BillingPeriod, interval int) (Subscription, error)
- func (s *Service) Get(ctx context.Context, id string) (Subscription, error)
- func (s *Service) GetByStripeSubscriptionID(ctx context.Context, stripeSubID string) (Subscription, error)
- func (s *Service) GetByWallet(ctx context.Context, wallet, productID string) (Subscription, error)
- func (s *Service) HandleStripeCancelled(ctx context.Context, stripeSubID string) error
- func (s *Service) HandleStripePaymentFailed(ctx context.Context, stripeSubID string) error
- func (s *Service) HandleStripeRenewal(ctx context.Context, stripeSubID string, periodStart, periodEnd time.Time) error
- func (s *Service) HandleStripeSubscriptionUpdated(ctx context.Context, stripeSubID string, update StripeSubscriptionUpdate) error
- func (s *Service) HasAccess(ctx context.Context, wallet, productID string) (bool, *Subscription, error)
- func (s *Service) HasStripeAccess(ctx context.Context, stripeSubID string) (bool, *Subscription, error)
- func (s *Service) ListExpiring(ctx context.Context, within time.Duration) ([]Subscription, error)
- func (s *Service) ReactivateSubscription(ctx context.Context, id string) (Subscription, error)
- type Status
- type StripeSubscriptionUpdate
- type Subscription
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 ¶
func (r *MemoryRepository) Create(_ context.Context, sub Subscription) error
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 ¶
func (r *MemoryRepository) Get(_ context.Context, id string) (Subscription, error)
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 ¶
func (r *MemoryRepository) Update(_ context.Context, sub Subscription) error
Update modifies an existing subscription.
func (*MemoryRepository) UpdateStatus ¶
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 ¶
func (r *PostgresRepository) Get(ctx context.Context, id string) (Subscription, error)
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 ¶
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) 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 ¶
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) GetByStripeSubscriptionID ¶
func (s *Service) GetByStripeSubscriptionID(ctx context.Context, stripeSubID string) (Subscription, error)
GetByStripeSubscriptionID retrieves a subscription by Stripe subscription ID.
func (*Service) GetByWallet ¶
GetByWallet retrieves a subscription by wallet and product.
func (*Service) HandleStripeCancelled ¶
HandleStripeCancelled marks a subscription as cancelled.
func (*Service) HandleStripePaymentFailed ¶
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 ¶
ListExpiring returns subscriptions expiring within the given duration.
func (*Service) ReactivateSubscription ¶
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.