Documentation
¶
Overview ¶
Package usersub holds the subscription domain's user-subscription entities (ADR-001 step 5: entity packages follow table ownership).
Index ¶
- Constants
- Variables
- func AcceptableToken(raw string) bool
- func ExpiredCondition(now time.Time) (string, []any)
- func ExpiringCondition(from, to time.Time) (string, []any)
- func ExpiryFromMilli(ms int64) time.Time
- func NewToken() string
- func NoExpiry(expireTime time.Time) bool
- func NoLimitBound() time.Time
- func NoLimitExpiry() time.Time
- func OnHold(status uint8) bool
- func ServableCondition(now time.Time) (string, []any)
- func UnexpiredCondition(now time.Time) (string, []any)
- type Availability
- type StatusSet
- type Subscribe
- func (s *Subscribe) AvailabilityAt(now time.Time) Availability
- func (s *Subscribe) ExpiredAt(now time.Time) bool
- func (s *Subscribe) GetCacheKeys() []string
- func (s *Subscribe) ReactivatedByTrafficReset(now time.Time) bool
- func (s *Subscribe) ResetTraffic(now time.Time) []string
- func (s *Subscribe) ServableAt(now time.Time) bool
- func (*Subscribe) TableName() string
- func (s *Subscribe) TrafficExhausted() bool
- type SubscribeDetails
- type SubscribeFilter
- type SubscribeLogFilterParams
Constants ¶
const ( SubscribeStatusPending uint8 = iota SubscribeStatusActive SubscribeStatusFinished SubscribeStatusExpired SubscribeStatusDeducted SubscribeStatusStopped )
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.
const TrafficExhaustedCondition = "traffic > 0 AND upload + download >= traffic"
TrafficExhaustedCondition is TrafficExhausted.
Variables ¶
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, } )
var ErrProviderManaged = errors.New("subscription is managed by its payment provider")
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.
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
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
ExpiredCondition is ExpiredAt(now); a NULL expiry never matches.
func ExpiringCondition ¶ added in v1.21.0
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
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
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
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
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
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
ServableCondition is ServableAt(now). NULL traffic counters are never exhausted (IS NOT TRUE), matching the zero values they read as.
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.
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
ExpiredAt reports whether the subscription's term is over at now. A subscription without a time limit never expires.
func (*Subscribe) GetCacheKeys ¶
func (*Subscribe) ReactivatedByTrafficReset ¶ added in v1.21.0
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
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
ServableAt reports whether the subscription may use the service at now.
func (*Subscribe) TrafficExhausted ¶ added in v1.21.0
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 ¶
SubscribeFilter filters user subscriptions.