Help us shape the future of Go! Take the 2026 Go Developer Survey

usersub

package
v1.21.2 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: GPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package usersub holds the subscription domain's user-subscription entities (ADR-001 step 5: entity packages follow table ownership).

Index

Constants

View Source
const (
	SubscribeStatusPending uint8 = iota
	SubscribeStatusActive
	SubscribeStatusFinished
	SubscribeStatusExpired
	SubscribeStatusDeducted
	SubscribeStatusStopped
)
View Source
const MinTokenLength = 8

MinTokenLength is the shortest token delivery resolves. Every token the server issued has 32 characters (NewToken and its predecessors); the bound is deliberately looser than that so it only turns away what can never be a token, the empty string above all: an empty token used to match the rows of older versions that stored none, and every probe cost a database query and a negative cache entry.

View Source
const TrafficExhaustedCondition = "traffic > 0 AND upload + download >= traffic"

TrafficExhaustedCondition is TrafficExhausted.

Variables

View Source
var (
	// LiveStatuses may use the service while their term and traffic last.
	// No write path creates Pending any more; rows left from older versions
	// are served like Active ones.
	LiveStatuses = StatusSet{SubscribeStatusPending, SubscribeStatusActive}
	// InTermStatuses are in their paid term, with or without traffic left:
	// the calendar traffic reset, the owner's node list and the token rotation
	// of all subscriptions apply to them.
	InTermStatuses = StatusSet{SubscribeStatusActive, SubscribeStatusFinished}
	// CurrentStatuses have not ended, been refunded or been stopped. Only they
	// may be cancelled by their owner, and marketing counts them as active.
	CurrentStatuses = StatusSet{SubscribeStatusPending, SubscribeStatusActive, SubscribeStatusFinished}
	// EndedStatuses are the ones marketing counts as inactive.
	EndedStatuses = StatusSet{SubscribeStatusExpired, SubscribeStatusStopped}
	// OwnerVisibleStatuses are listed to the subscription's owner; a refunded
	// or stopped subscription is not.
	OwnerVisibleStatuses = StatusSet{SubscribeStatusPending, SubscribeStatusActive, SubscribeStatusFinished, SubscribeStatusExpired}
	// HeldStatuses were refunded (Deducted) or stopped by an administrator.
	// Only an administrator brings them back; renewals, resets and provider
	// updates keep the hold.
	HeldStatuses = StatusSet{SubscribeStatusDeducted, SubscribeStatusStopped}
	// AllStatuses lists every status, for administrators.
	AllStatuses = StatusSet{
		SubscribeStatusPending, SubscribeStatusActive, SubscribeStatusFinished,
		SubscribeStatusExpired, SubscribeStatusDeducted, SubscribeStatusStopped,
	}
)
View Source
var ErrProviderManaged = errors.New("subscription is managed by its payment provider")
View Source
var ErrSubscriptionOnHold = errors.New("subscription is refunded or stopped")

ErrSubscriptionOnHold rejects a renewal or traffic reset that would bring back a refunded or administrator-stopped subscription.

View Source
var LocalControlColumns = []string{"token", "uuid", "note", "upload", "download", "status", "finished_at"}

LocalControlColumns are the user_subscribe columns local operations may write on a provider-managed subscription: credentials, the owner's note, the traffic counters and the administrative hold. The payment provider owns every other column (plan, order, term).

Functions

func AcceptableToken added in v1.21.0

func AcceptableToken(raw string) bool

AcceptableToken reports whether raw may be a stored token and is worth a lookup: at least MinTokenLength characters, none of them whitespace or a control character.

func ExpiredCondition added in v1.21.0

func ExpiredCondition(now time.Time) (string, []any)

ExpiredCondition is ExpiredAt(now); a NULL expiry never matches.

func ExpiringCondition added in v1.21.0

func ExpiringCondition(from, to time.Time) (string, []any)

ExpiringCondition selects the terms ending at or after from and before to. A no-limit expiry never does: a window starting below NoLimitBound is cut there.

func ExpiryFromMilli added in v1.21.0

func ExpiryFromMilli(ms int64) time.Time

ExpiryFromMilli returns the expire_time for a term end given in Unix milliseconds by the administration API, where 0 means "no time limit". Any value NoExpiry accepts is stored as the marker NoLimitExpiry writes.

func NewToken added in v1.21.0

func NewToken() string

NewToken returns a new subscription token: 128 bits from crypto/rand, hex-encoded to the 32 characters stored tokens already have. The token is the only credential of the subscription URL, so it must not be derivable from anything another party can see, such as an order number or the time it was issued.

func NoExpiry added in v1.21.0

func NoExpiry(expireTime time.Time) bool

NoExpiry reports whether expireTime means "no time limit": any value before NoLimitBound, which covers the epoch marker read in any zone and the zero time a NULL expire_time reads as.

func NoLimitBound added in v1.21.0

func NoLimitBound() time.Time

NoLimitBound returns the earliest expire_time that is a real term end. A stored expiry before it means "no time limit" whatever wall clock it was written in: the epoch lands within a day of 1970-01-01 in every zone, and no term ever ended in 1970. SQL conditions bind it as a parameter and compare with it instead of testing equality with NoLimitExpiry, so rows written in different wall clocks are all recognised.

func NoLimitExpiry added in v1.21.0

func NoLimitExpiry() time.Time

NoLimitExpiry returns the expire_time written for a subscription without a time limit: the Unix epoch in the application zone. It is read at call time, never stored in a package variable: the process zone is set at startup (timeutil.LoadProcessLocation), and a zone-less database column (PostgreSQL's timestamp) keeps the writer's wall clock, so a marker built at package init would be stored as another value than one built later.

func OnHold added in v1.21.0

func OnHold(status uint8) bool

OnHold reports whether the subscription was refunded (Deducted) or stopped by an administrator. Only an administrator may bring it back; a user purchase must not.

func ServableCondition added in v1.21.0

func ServableCondition(now time.Time) (string, []any)

ServableCondition is ServableAt(now). NULL traffic counters are never exhausted (IS NOT TRUE), matching the zero values they read as.

func UnexpiredCondition added in v1.21.0

func UnexpiredCondition(now time.Time) (string, []any)

UnexpiredCondition is the negation of ExpiredAt(now).

Types

type Availability added in v1.21.0

type Availability uint8

Availability says whether a subscription may use the service at a given moment and, if not, why. It is the one answer to "can this subscription be served?": delivery, the storefront node list, the edge manifest and, through ServableCondition, the node user list all ask it.

const (
	// Available subscriptions reach real nodes.
	Available Availability = iota
	// Refunded subscriptions were cancelled with a refund (Deducted).
	Refunded
	// Stopped subscriptions are held by an administrator.
	Stopped
	// Expired subscriptions are past their term.
	Expired
	// TrafficExhausted subscriptions used up their traffic quota.
	TrafficExhausted
	// Inactive covers any status no rule serves.
	Inactive
)

type StatusSet added in v1.21.0

type StatusSet []uint8

StatusSet names a group of subscription statuses that one business rule treats alike. The rules below are the only places that enumerate statuses; code that filters or branches on a status uses them instead of numbers.

func (StatusSet) Contains added in v1.21.0

func (s StatusSet) Contains(status uint8) bool

Contains reports whether status belongs to the set.

func (StatusSet) Values added in v1.21.0

func (s StatusSet) Values() []int64

Values returns the set as SQL parameters. A []uint8 is a []byte to the database driver and would bind as one binary value instead of a list.

type Subscribe

type Subscribe struct {
	// Empty means locally managed. Provider-managed expiry cannot be edited
	// through ordinary renewal, refund or administration commands.
	EntitlementSource string `gorm:"type:varchar(32);not null;default:''"`

	Id          int64      `gorm:"primaryKey"`
	UserId      int64      `gorm:"index:idx_user_id;not null;comment:User ID"`
	OrderId     int64      `gorm:"index:idx_order_id;not null;comment:Order ID"`
	SubscribeId int64      `gorm:"index:idx_subscribe_id;not null;comment:Subscription ID"`
	StartTime   time.Time  `gorm:"default:CURRENT_TIMESTAMP(3);not null;comment:Subscription Start Time"`
	ExpireTime  time.Time  `gorm:"default:NULL;comment:Subscription Expire Time"`
	FinishedAt  *time.Time `gorm:"default:NULL;comment:Finished Time"`
	Traffic     int64      `gorm:"default:0;comment:Traffic"`
	Download    int64      `gorm:"default:0;comment:Download Traffic"`
	Upload      int64      `gorm:"default:0;comment:Upload Traffic"`
	Token       string     `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
	UUID        string     `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
	Status      uint8      `` /* 128-byte string literal not displayed */
	Note        string     `gorm:"type:varchar(500);default:'';comment:User note for subscription"`
	// TrafficResetAt is the start of the day of the last calendar traffic
	// reset. It is that reset's done-marker: a retried reset run skips the
	// subscriptions already reset for the day.
	TrafficResetAt *time.Time `gorm:"default:NULL;comment:Last Calendar Traffic Reset Day"`
	CreatedAt      time.Time  `gorm:"<-:create;comment:Creation Time"`
	UpdatedAt      time.Time  `gorm:"comment:Update Time"`
}

func (*Subscribe) AvailabilityAt added in v1.21.0

func (s *Subscribe) AvailabilityAt(now time.Time) Availability

AvailabilityAt classifies the subscription at now. A hold wins over everything, an elapsed term over the traffic state, so the reason shown is the one the owner must resolve first.

func (*Subscribe) ExpiredAt added in v1.21.0

func (s *Subscribe) ExpiredAt(now time.Time) bool

ExpiredAt reports whether the subscription's term is over at now. A subscription without a time limit never expires.

func (*Subscribe) GetCacheKeys

func (s *Subscribe) GetCacheKeys() []string

func (*Subscribe) ReactivatedByTrafficReset added in v1.21.0

func (s *Subscribe) ReactivatedByTrafficReset(now time.Time) bool

ReactivatedByTrafficReset reports whether clearing the traffic counters at now brings the subscription back: only a Finished (traffic-exhausted) one inside its term. A hold is never lifted by a reset and an expired subscription needs a renewal. Every traffic reset follows this rule: the calendar reset, the paid one, the quota task and the administrator's.

func (*Subscribe) ResetTraffic added in v1.21.0

func (s *Subscribe) ResetTraffic(now time.Time) []string

ResetTraffic clears the traffic counters, reactivates the subscription when ReactivatedByTrafficReset allows it, and returns the columns it changed.

func (*Subscribe) ServableAt added in v1.21.0

func (s *Subscribe) ServableAt(now time.Time) bool

ServableAt reports whether the subscription may use the service at now.

func (*Subscribe) TableName

func (*Subscribe) TableName() string

func (*Subscribe) TrafficExhausted added in v1.21.0

func (s *Subscribe) TrafficExhausted() bool

TrafficExhausted reports whether the traffic quota is used up; a zero quota is unlimited. It compares without adding the counters, so it cannot overflow.

type SubscribeDetails

type SubscribeDetails struct {
	EntitlementSource string

	Id          int64                `gorm:"primarykey"`
	UserId      int64                `gorm:"index:idx_user_id;not null;comment:User ID"`
	OrderId     int64                `gorm:"index:idx_order_id;not null;comment:Order ID"`
	SubscribeId int64                `gorm:"index:idx_subscribe_id;not null;comment:Subscription ID"`
	Subscribe   *subscribe.Subscribe `gorm:"foreignKey:SubscribeId;references:Id"`
	StartTime   time.Time            `gorm:"default:CURRENT_TIMESTAMP(3);not null;comment:Subscription Start Time"`
	ExpireTime  time.Time            `gorm:"default:NULL;comment:Subscription Expire Time"`
	FinishedAt  *time.Time           `gorm:"default:NULL;comment:Finished Time"`
	Traffic     int64                `gorm:"default:0;comment:Traffic"`
	Download    int64                `gorm:"default:0;comment:Download Traffic"`
	Upload      int64                `gorm:"default:0;comment:Upload Traffic"`
	Token       string               `gorm:"index:idx_token;unique;type:varchar(255);default:'';comment:Token"`
	UUID        string               `gorm:"type:varchar(255);unique;index:idx_uuid;default:'';comment:UUID"`
	Status      uint8                `gorm:"type:tinyint(1);default:0;comment:Subscription Status: 0: Pending 1: Active 2: Finished 3: Expired; 4: Cancelled"`
	Note        string               `gorm:"type:varchar(500);default:'';comment:User note for subscription"`
	CreatedAt   time.Time            `gorm:"<-:create;comment:Creation Time"`
	UpdatedAt   time.Time            `gorm:"comment:Update Time"`
}

SubscribeDetails is the subscription row with its plan association. The identity owner is composed at the module layer through the identity read port, never through a cross-domain association (ADR-001 step 5).

func (*SubscribeDetails) TableName

func (*SubscribeDetails) TableName() string

type SubscribeFilter

type SubscribeFilter struct {
	Subscribers []int64
	IsActive    *bool
	StartTime   int64
	EndTime     int64
}

SubscribeFilter filters user subscriptions.

type SubscribeLogFilterParams

type SubscribeLogFilterParams struct {
	IP              string
	UserAgent       string
	UserId          int64
	Token           string
	UserSubscribeId int64
}

SubscribeLogFilterParams filters user subscribe access logs.

Jump to

Keyboard shortcuts

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