subscription

package
v1.0.0-beta.222 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2025 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// AnnotationSubscriptionID is the ID of the subscription that created this entitlement
	AnnotationSubscriptionID = "subscription.id"

	AnnotationOwnerSubSystem = "subscription.owner"

	AnnotationBooleanEntitlementCount = "subscription.entitlement.boolean.count"
)
View Source
const ErrCodeSubscriptionBillingAnchorIsRequired models.ErrorCode = "subscription_billing_anchor_is_required"
View Source
const ErrCodeSubscriptionBillingPeriodQueriedBeforeSubscriptionStart models.ErrorCode = "subscription_billing_period_queried_before_subscription_start"
View Source
const ErrCodeSubscriptionItemActiveFromOverrideRelativeToPhaseStartIsNegative models.ErrorCode = "subscription_item_active_from_override_relative_to_phase_start_is_negative"
View Source
const ErrCodeSubscriptionItemActiveToOverrideRelativeToPhaseStartIsNegative models.ErrorCode = "subscription_item_active_to_override_relative_to_phase_start_is_negative"
View Source
const ErrCodeSubscriptionItemBillingOverrideIsOnlyAllowedForBillableItems models.ErrorCode = "subscription_item_billing_override_is_only_allowed_for_billable_items"
View Source
const ErrCodeSubscriptionItemHistoryOverlap models.ErrorCode = "subscription_item_history_overlap"
View Source
const ErrCodeSubscriptionPhaseHasNoItems models.ErrorCode = "subscription_phase_has_no_items"
View Source
const ErrCodeSubscriptionPhaseItemHistoryKeyMismatch models.ErrorCode = "subscription_phase_item_history_key_mismatch"
View Source
const ErrCodeSubscriptionPhaseItemKeyMismatchWithPhaseKey models.ErrorCode = "subscription_phase_item_key_mismatch_with_phase_key"
View Source
const ErrCodeSubscriptionPhaseStartAfterIsNegative models.ErrorCode = "subscription_phase_start_after_is_negative"
View Source
const (
	EventSubsystem metadata.EventSubsystem = "subscription"
)
View Source
const OwnerSubscriptionSubSystem = "subscription"

Variables

View Source
var AnnotationParser = annotationParser{}
View Source
var ErrSubscriptionBillingAnchorIsRequired = models.NewValidationIssue(
	ErrCodeSubscriptionBillingAnchorIsRequired,
	"billing anchor is required",
	models.WithFieldString("billingAnchor"),
)
View Source
var ErrSubscriptionBillingPeriodQueriedBeforeSubscriptionStart = models.NewValidationIssue(
	ErrCodeSubscriptionBillingPeriodQueriedBeforeSubscriptionStart,
	"billing period queried before subscription start",
)
View Source
var ErrSubscriptionItemActiveFromOverrideRelativeToPhaseStartIsNegative = models.NewValidationIssue(
	ErrCodeSubscriptionItemActiveFromOverrideRelativeToPhaseStartIsNegative,
	"active from override relative to phase start cannot be negative",
	models.WithFieldString("activeFromOverrideRelativeToPhaseStart"),
)
View Source
var ErrSubscriptionItemActiveToOverrideRelativeToPhaseStartIsNegative = models.NewValidationIssue(
	ErrCodeSubscriptionItemActiveToOverrideRelativeToPhaseStartIsNegative,
	"active to override relative to phase start cannot be negative",
	models.WithFieldString("activeToOverrideRelativeToPhaseStart"),
)
View Source
var ErrSubscriptionItemBillingOverrideIsOnlyAllowedForBillableItems = models.NewValidationIssue(
	ErrCodeSubscriptionItemBillingOverrideIsOnlyAllowedForBillableItems,
	"billing override is only allowed for billable items",
	models.WithFieldString("billingBehaviorOverride"),
)
View Source
var ErrSubscriptionItemHistoryOverlap = models.NewValidationIssue(
	ErrCodeSubscriptionItemHistoryOverlap,
	"subscription item history overlap",
	models.WithFieldString("itemsByKey"),
)
View Source
var ErrSubscriptionPhaseHasNoItems = models.NewValidationIssue(
	ErrCodeSubscriptionPhaseHasNoItems,
	"subscription phase must have at least one item",
	models.WithFieldString("items"),
)
View Source
var ErrSubscriptionPhaseItemHistoryKeyMismatch = models.NewValidationIssue(
	ErrCodeSubscriptionPhaseItemHistoryKeyMismatch,
	"subscription phase item history key mismatch",
	models.WithFieldString("itemsByKey"),
)
View Source
var ErrSubscriptionPhaseItemKeyMismatchWithPhaseKey = models.NewValidationIssue(
	ErrCodeSubscriptionPhaseItemKeyMismatchWithPhaseKey,
	"subscription phase item key mismatch with phase key",
	models.WithFieldString("itemKey"),
	models.WithFieldString("phaseKey"),
)
View Source
var ErrSubscriptionPhaseStartAfterIsNegative = models.NewValidationIssue(
	ErrCodeSubscriptionPhaseStartAfterIsNegative,
	"subscription phase start after cannot be negative",
	models.WithFieldString("startAfter"),
)

Functions

func GetCustomerLock

func GetCustomerLock(customerId string) (lockr.Key, error)

func IsItemNotFoundError

func IsItemNotFoundError(err error) bool

IsItemNotFoundError returns true if the error is a ItemNotFoundError.

func IsPhaseNotFoundError

func IsPhaseNotFoundError(err error) bool

IsPhaseNotFoundError returns true if the error is a PhaseNotFoundError.

func IsPlanNotFoundError

func IsPlanNotFoundError(err error) bool

IsPlanNotFoundError returns true if the error is a PlanNotFoundError.

func IsSubscriptionNotFoundError

func IsSubscriptionNotFoundError(err error) bool

IsSubscriptionNotFoundError returns true if the error is a SubscriptionNotFoundError.

func IsValidationIssueWithBoolAttr

func IsValidationIssueWithBoolAttr(err error, attrName string) bool

func IsValidationIssueWithCode

func IsValidationIssueWithCode(err error, code models.ErrorCode) bool

func MapSubscriptionSpecValidationIssueFieldSelectors

func MapSubscriptionSpecValidationIssueFieldSelectors(iss models.ValidationIssue) (models.ValidationIssue, error)

MapSubscriptionSpecValidationIssueFieldSelectors maps the FieldSelectors of a ValidationIssue from the structure of SubscriptionSpec to the structure of api.SubscriptionView

func NewErrSubscriptionBillingPeriodQueriedBeforeSubscriptionStart

func NewErrSubscriptionBillingPeriodQueriedBeforeSubscriptionStart(queriedAt, subscriptionStart time.Time) error

func NewItemNotFoundError

func NewItemNotFoundError(itemId string) error

NewItemNotFoundError returns a new ItemNotFoundError.

func NewPhaseNotFoundError

func NewPhaseNotFoundError(phaseId string) error

NewPhaseNotFoundError returns a new PhaseNotFoundError.

func NewPlanNotFoundError

func NewPlanNotFoundError(key string, version int) error

NewPlanNotFoundError returns a new PlanNotFoundError.

func NewSubscriptionNotFoundError

func NewSubscriptionNotFoundError(id string) error

NewSubscriptionNotFoundError returns a new SubscriptionNotFoundError.

Types

type AlignmentError

type AlignmentError struct {
	Inner error
}

AlignmentError is an error that occurs when the spec is not aligned but we expect it to be.

func (AlignmentError) Error

func (e AlignmentError) Error() string

func (AlignmentError) Unwrap

func (e AlignmentError) Unwrap() error

type AnyValuePatch

type AnyValuePatch interface {
	ValueAsAny() any
}

type AppliesToSpec

type AppliesToSpec interface {
	// This method should only ever be invoked by SubscriptionSpec (spec.ApplyX), so subsequent validations and logic are always guaranteed to run
	// FIXME(galexi): this can be enforced by making it private, but that will require rewriting all patches & addons
	ApplyTo(spec *SubscriptionSpec, actx ApplyContext) error
}

Things can apply themselves to the spec

func NewAggregateAppliesToSpec

func NewAggregateAppliesToSpec(applieses []AppliesToSpec) AppliesToSpec

NewAggregateAppliesToSpec aggregates multiple applies to spec into a single applies to spec, and also validates the spec after applying all the patches

func NewAppliesToSpec

func NewAppliesToSpec(fn func(spec *SubscriptionSpec, actx ApplyContext) error) AppliesToSpec

func ToApplies

func ToApplies(p Patch, _ int) AppliesToSpec

type ApplyContext

type ApplyContext struct {
	CurrentTime time.Time
}

type BillingBehaviorOverride

type BillingBehaviorOverride struct {
	// If true, the billing cadence will be restarted
	// The anchor time will be the time the originating change takes effect,
	// which in practive translates to a SubscriptionItem's ActiveFrom property.
	RestartBillingPeriod *bool `json:"restartBillingPeriod,omitempty"`
}

type CancelledEvent

type CancelledEvent viewEvent

func NewCancelledEvent

func NewCancelledEvent(ctx context.Context, view SubscriptionView) CancelledEvent

NewCancelledEvent creates a new deleted event

func (CancelledEvent) EventMetadata

func (s CancelledEvent) EventMetadata() metadata.EventMetadata

func (CancelledEvent) EventName

func (s CancelledEvent) EventName() string

func (CancelledEvent) Validate

func (s CancelledEvent) Validate() error

type ContinuedEvent

type ContinuedEvent viewEvent

func NewContinuedEvent

func NewContinuedEvent(ctx context.Context, view SubscriptionView) ContinuedEvent

NewContinuedEvent creates a new continued event

func (ContinuedEvent) EventMetadata

func (s ContinuedEvent) EventMetadata() metadata.EventMetadata

func (ContinuedEvent) EventName

func (s ContinuedEvent) EventName() string

func (ContinuedEvent) Validate

func (s ContinuedEvent) Validate() error

type CreateSubscriptionCustomerInput

type CreateSubscriptionCustomerInput struct {
	models.MetadataModel `json:",inline"`
	Name                 string         `json:"name"`
	Description          *string        `json:"description,omitempty"`
	CustomerId           string         `json:"customerId"`
	Currency             currencyx.Code `json:"currency"`
	ActiveFrom           time.Time      `json:"activeFrom,omitempty"`
	ActiveTo             *time.Time     `json:"activeTo,omitempty"`
	BillingAnchor        time.Time      `json:"billingAnchor,omitempty"`
}

type CreateSubscriptionEntityInput

type CreateSubscriptionEntityInput struct {
	models.CadencedModel
	models.NamespacedModel
	models.MetadataModel

	Plan        *PlanRef
	Name        string  `json:"name,omitempty"`
	Description *string `json:"description,omitempty"`

	CustomerId string `json:"customerId,omitempty"`
	Currency   currencyx.Code

	// BillingCadence is the default billing cadence for subscriptions.
	BillingCadence datetime.ISODuration `json:"billing_cadence"`

	// ProRatingConfig is the default pro-rating configuration for subscriptions.
	ProRatingConfig productcatalog.ProRatingConfig `json:"pro_rating_config"`

	// BillingAnchor is the time the subscription will be billed.
	BillingAnchor time.Time `json:"billingAnchor"`
}

type CreateSubscriptionItemCustomerInput

type CreateSubscriptionItemCustomerInput struct {
	ActiveFromOverrideRelativeToPhaseStart *datetime.ISODuration `json:"activeFromOverrideRelativeToPhaseStart,omitempty"`
	ActiveToOverrideRelativeToPhaseStart   *datetime.ISODuration `json:"activeToOverrideRelativeToPhaseStart,omitempty"`
	BillingBehaviorOverride
}

func (*CreateSubscriptionItemCustomerInput) UnmarshalJSON

func (i *CreateSubscriptionItemCustomerInput) UnmarshalJSON(b []byte) error

type CreateSubscriptionItemEntityInput

type CreateSubscriptionItemEntityInput struct {
	models.NamespacedModel
	models.MetadataModel

	Annotations models.Annotations `json:"annotations"`

	ActiveFromOverrideRelativeToPhaseStart *datetime.ISODuration
	ActiveToOverrideRelativeToPhaseStart   *datetime.ISODuration

	models.CadencedModel

	BillingBehaviorOverride BillingBehaviorOverride

	// PhaseID is the ID of the phase this item belongs to.
	PhaseID string

	// Key is the unique key of the item in the phase.
	Key string

	RateCard productcatalog.RateCard

	EntitlementID *string
	Name          string  `json:"name,omitempty"`
	Description   *string `json:"description,omitempty"`
}

func (CreateSubscriptionItemEntityInput) Equal

type CreateSubscriptionItemInput

type CreateSubscriptionItemInput struct {
	Annotations                         models.Annotations `json:"annotations"`
	CreateSubscriptionItemPlanInput     `json:",inline"`
	CreateSubscriptionItemCustomerInput `json:",inline"`
}

func (*CreateSubscriptionItemInput) UnmarshalJSON

func (i *CreateSubscriptionItemInput) UnmarshalJSON(b []byte) error

type CreateSubscriptionItemPlanInput

type CreateSubscriptionItemPlanInput struct {
	PhaseKey string                  `json:"phaseKey"`
	ItemKey  string                  `json:"itemKey"`
	RateCard productcatalog.RateCard `json:"rateCard"`
}

func (*CreateSubscriptionItemPlanInput) UnmarshalJSON

func (i *CreateSubscriptionItemPlanInput) UnmarshalJSON(b []byte) error

type CreateSubscriptionPhaseCustomerInput

type CreateSubscriptionPhaseCustomerInput struct {
	models.MetadataModel `json:",inline"`
}

type CreateSubscriptionPhaseEntityInput

type CreateSubscriptionPhaseEntityInput struct {
	models.NamespacedModel
	models.MetadataModel

	// ActiveFrom is the time the phase becomes active.
	ActiveFrom time.Time

	// SubscriptionID is the ID of the subscription this phase belongs to.
	SubscriptionID string `json:"subscriptionId"`

	// Key is the unique key for Phase.
	Key string `json:"key"`

	// Name
	Name string `json:"name"`

	// Description
	Description *string `json:"description,omitempty"`

	// StartAfter
	StartAfter datetime.ISODuration `json:"interval"`

	// SortHint
	SortHint *uint8 `json:"sortHint,omitempty"`
}

func (CreateSubscriptionPhaseEntityInput) Equal

type CreateSubscriptionPhaseInput

type CreateSubscriptionPhaseInput struct {
	// Duration is required exactly in cases where the phase wouldn't be the last phase.
	Duration *datetime.ISODuration `json:"duration"`
	CreateSubscriptionPhasePlanInput
	CreateSubscriptionPhaseCustomerInput
}

func (CreateSubscriptionPhaseInput) Validate

func (i CreateSubscriptionPhaseInput) Validate() error

type CreateSubscriptionPhasePlanInput

type CreateSubscriptionPhasePlanInput struct {
	PhaseKey    string               `json:"key"`
	StartAfter  datetime.ISODuration `json:"startAfter"`
	Name        string               `json:"name"`
	Description *string              `json:"description,omitempty"`
	SortHint    *uint8               `json:"sortHint,omitempty"`
}

func (CreateSubscriptionPhasePlanInput) Validate

type CreateSubscriptionPlanInput

type CreateSubscriptionPlanInput struct {
	Plan *PlanRef `json:"plan"`

	// BillingCadence is the default billing cadence for subscriptions.
	BillingCadence datetime.ISODuration `json:"billing_cadence"`

	// ProRatingConfig is the default pro-rating configuration for subscriptions.
	ProRatingConfig productcatalog.ProRatingConfig `json:"pro_rating_config"`
}

type CreatedEvent

type CreatedEvent viewEvent

func NewCreatedEvent

func NewCreatedEvent(ctx context.Context, view SubscriptionView) CreatedEvent

NewCreatedEvent creates a new created event

func (CreatedEvent) EventMetadata

func (s CreatedEvent) EventMetadata() metadata.EventMetadata

func (CreatedEvent) EventName

func (s CreatedEvent) EventName() string

func (CreatedEvent) Validate

func (s CreatedEvent) Validate() error

type DeletedEvent

type DeletedEvent viewEvent

func NewDeletedEvent

func NewDeletedEvent(ctx context.Context, view SubscriptionView) DeletedEvent

NewDeletedEvent creates a new deleted event

func (DeletedEvent) EventMetadata

func (s DeletedEvent) EventMetadata() metadata.EventMetadata

func (DeletedEvent) EventName

func (s DeletedEvent) EventName() string

func (DeletedEvent) Validate

func (s DeletedEvent) Validate() error

type EntitlementAdapter

type EntitlementAdapter interface {
	ScheduleEntitlement(ctx context.Context, input ScheduleSubscriptionEntitlementInput, annotations models.Annotations) (*SubscriptionEntitlement, error)
	// At refers to a point in time for which we're querying the system state, meaning:
	// if t1 < t2 < t3, and some entitlement was deleted effective at t2, then
	// with at = t1 the entitlement will be returned, while with at = t3 it won't.
	GetForSubscriptionAt(ctx context.Context, subscriptionID models.NamespacedID, at time.Time) ([]SubscriptionEntitlement, error)

	DeleteByItemID(ctx context.Context, itemId models.NamespacedID) error
}

type GetFullServicePeriodAtInput

type GetFullServicePeriodAtInput struct {
	SubscriptionCadence  models.CadencedModel
	PhaseCadence         models.CadencedModel
	ItemCadence          models.CadencedModel
	At                   time.Time
	AlignedBillingAnchor time.Time
}

func (GetFullServicePeriodAtInput) Validate

func (i GetFullServicePeriodAtInput) Validate() error

type ItemNotFoundError

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

ItemNotFoundError is returned when a meter is not found.

func (*ItemNotFoundError) Error

func (e *ItemNotFoundError) Error() string

Error returns the error message.

func (*ItemNotFoundError) Unwrap

func (e *ItemNotFoundError) Unwrap() error

Unwrap returns the wrapped error.

type ListSubscriptionsInput

type ListSubscriptionsInput struct {
	pagination.Page

	Namespaces     []string
	Customers      []string
	ActiveAt       *time.Time
	ActiveInPeriod *timeutil.ClosedPeriod
}

func (ListSubscriptionsInput) Validate

func (i ListSubscriptionsInput) Validate() error

type NoBillingPeriodError

type NoBillingPeriodError struct {
	Inner error
}

NoBillingPeriodError is an error that occurs when a phase has no billing period.

func (NoBillingPeriodError) Error

func (e NoBillingPeriodError) Error() string

func (NoBillingPeriodError) Unwrap

func (e NoBillingPeriodError) Unwrap() error

type NoOpSubscriptionValidator

type NoOpSubscriptionValidator struct{}

func (NoOpSubscriptionValidator) ValidateCancel

func (NoOpSubscriptionValidator) ValidateContinue

func (NoOpSubscriptionValidator) ValidateCreate

func (NoOpSubscriptionValidator) ValidateDelete

func (NoOpSubscriptionValidator) ValidateUpdate

type Patch

type Patch interface {
	AppliesToSpec
	Validate() error
	Op() PatchOperation
	Path() SpecPath
}

type PatchConflictError

type PatchConflictError struct {
	Msg string
}

func (*PatchConflictError) Error

func (e *PatchConflictError) Error() string

type PatchForbiddenError

type PatchForbiddenError struct {
	Msg string
}

func (*PatchForbiddenError) Error

func (e *PatchForbiddenError) Error() string

type PatchOperation

type PatchOperation string
const (
	PatchOperationAdd        PatchOperation = "add"
	PatchOperationRemove     PatchOperation = "remove"
	PatchOperationUnschedule PatchOperation = "unschedule"
	PatchOperationStretch    PatchOperation = "stretch"
)

func (PatchOperation) Validate

func (o PatchOperation) Validate() error

type PatchValidationError

type PatchValidationError struct {
	Msg string
}

func (*PatchValidationError) Error

func (e *PatchValidationError) Error() string

type PhaseNotFoundError

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

PhaseNotFoundError is returned when a meter is not found.

func (*PhaseNotFoundError) Error

func (e *PhaseNotFoundError) Error() string

Error returns the error message.

func (*PhaseNotFoundError) Unwrap

func (e *PhaseNotFoundError) Unwrap() error

Unwrap returns the wrapped error.

type Plan

type Plan interface {
	ToCreateSubscriptionPlanInput() CreateSubscriptionPlanInput

	GetName() string

	// Phases are expected to be returned in the order they activate.
	GetPhases() []PlanPhase

	// Will not make sense on the long term
	Currency() currencyx.Code
}

All methods are expected to return stable values.

type PlanNotFoundError

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

PlanNotFoundError is returned when a meter is not found.

func (*PlanNotFoundError) Error

func (e *PlanNotFoundError) Error() string

Error returns the error message.

func (*PlanNotFoundError) Unwrap

func (e *PlanNotFoundError) Unwrap() error

Unwrap returns the wrapped error.

type PlanPhase

type PlanPhase interface {
	ToCreateSubscriptionPhasePlanInput() CreateSubscriptionPhasePlanInput
	GetRateCards() []PlanRateCard
	GetKey() string
}

All methods are expected to return stable values.

type PlanRateCard

type PlanRateCard interface {
	ToCreateSubscriptionItemPlanInput() CreateSubscriptionItemPlanInput
	GetKey() string
}

All methods are expected to return stable values.

type PlanRef

type PlanRef struct {
	Id      string `json:"id"`
	Key     string `json:"key"`
	Version int    `json:"version"`
}

func (PlanRef) Equal

func (p PlanRef) Equal(p2 PlanRef) bool

func (*PlanRef) NilEqual

func (p *PlanRef) NilEqual(p2 *PlanRef) bool

type RemoveSubscriptionPhaseInput

type RemoveSubscriptionPhaseInput struct {
	Shift RemoveSubscriptionPhaseShifting `json:"shift"`
}

type RemoveSubscriptionPhaseShifting

type RemoveSubscriptionPhaseShifting int
const (
	RemoveSubscriptionPhaseShiftNext RemoveSubscriptionPhaseShifting = iota
	RemoveSubscriptionPhaseShiftPrev
)

func (RemoveSubscriptionPhaseShifting) Validate

type ScheduleSubscriptionEntitlementInput

type ScheduleSubscriptionEntitlementInput struct {
	entitlement.CreateEntitlementInputs
}

func (ScheduleSubscriptionEntitlementInput) Equal

func (ScheduleSubscriptionEntitlementInput) Validate

type Service

type Service interface {
	// Create a new subscription accotding to the given spec
	Create(ctx context.Context, namespace string, spec SubscriptionSpec) (Subscription, error)
	// Update the subscription with the given ID to the target spec
	Update(ctx context.Context, subscriptionID models.NamespacedID, target SubscriptionSpec) (Subscription, error)
	// Delete a scheduled subscription with the given ID
	Delete(ctx context.Context, subscriptionID models.NamespacedID) error
	// Cancel a running subscription at the provided time
	Cancel(ctx context.Context, subscriptionID models.NamespacedID, timing Timing) (Subscription, error)
	// Continue a canceled subscription (effectively undoing the cancellation)
	Continue(ctx context.Context, subscriptionID models.NamespacedID) (Subscription, error)
	// Get the subscription with the given ID
	Get(ctx context.Context, subscriptionID models.NamespacedID) (Subscription, error)
	// GetView returns a full view of the subscription with the given ID
	GetView(ctx context.Context, subscriptionID models.NamespacedID) (SubscriptionView, error)
	// List lists the subscriptions matching the set criteria
	List(ctx context.Context, params ListSubscriptionsInput) (SubscriptionList, error)
	// GetAllForCustomerSince returns all subscriptions for the given customer that are active or scheduled to start after the given timestamp
	GetAllForCustomerSince(ctx context.Context, customerID models.NamespacedID, at time.Time) ([]Subscription, error)

	ValidatorService
}

type SpecPath

type SpecPath string

func NewItemPath

func NewItemPath(phaseKey, itemKey string) SpecPath

func NewItemVersionPath

func NewItemVersionPath(phaseKey, itemKey string, idx int) SpecPath

func NewPhasePath

func NewPhasePath(phaseKey string) SpecPath

func (SpecPath) IsParentOf

func (p SpecPath) IsParentOf(other SpecPath) bool

Checks whether p is a parent of other where parent means all segments of p are present and in order in other

func (SpecPath) ItemKey

func (p SpecPath) ItemKey() string

func (SpecPath) ItemVersion

func (p SpecPath) ItemVersion() int

func (SpecPath) MarshalJSON

func (p SpecPath) MarshalJSON() ([]byte, error)

Lets implement JSON Marshaler for Path

func (SpecPath) PhaseKey

func (p SpecPath) PhaseKey() string

func (SpecPath) Type

func (p SpecPath) Type() SpecPathType

func (*SpecPath) UnmarshalJSON

func (p *SpecPath) UnmarshalJSON(data []byte) error

Lets implement JSON Unmarshaler for Path

func (SpecPath) Validate

func (p SpecPath) Validate() error

Lets implement validation for Path

type SpecPathType

type SpecPathType string
const (
	SpecPathTypePhase       SpecPathType = "phase"
	SpecPathTypeItem        SpecPathType = "item"
	SpecPathTypeItemVersion SpecPathType = "item_version"
)

type SpecValidationError

type SpecValidationError struct {
	AffectedKeys [][]string
	Msg          string
}

func (*SpecValidationError) Error

func (e *SpecValidationError) Error() string

type Subscription

type Subscription struct {
	models.NamespacedID
	models.ManagedModel
	models.CadencedModel
	models.MetadataModel

	Name        string  `json:"name,omitempty"`
	Description *string `json:"description,omitempty"`

	// References the plan (if the Subscription was created form one)
	PlanRef *PlanRef `json:"planRef"`

	CustomerId string         `json:"customerId,omitempty"`
	Currency   currencyx.Code `json:"currency,omitempty"`

	BillingCadence  datetime.ISODuration           `json:"billing_cadence"`
	BillingAnchor   time.Time                      `json:"billingAnchor"`
	ProRatingConfig productcatalog.ProRatingConfig `json:"pro_rating_config"`
}

func (Subscription) AsEntityInput

func (s Subscription) AsEntityInput() CreateSubscriptionEntityInput

func (Subscription) GetCustomerID

func (s Subscription) GetCustomerID() customer.CustomerID

func (Subscription) GetStatusAt

func (s Subscription) GetStatusAt(at time.Time) SubscriptionStatus

type SubscriptionAction

type SubscriptionAction string
const (
	SubscriptionActionCreate       SubscriptionAction = "create"
	SubscriptionActionUpdate       SubscriptionAction = "update"
	SubscriptionActionCancel       SubscriptionAction = "cancel"
	SubscriptionActionContinue     SubscriptionAction = "continue"
	SubscriptionActionDelete       SubscriptionAction = "delete"
	SubscriptionActionChangeAddons SubscriptionAction = "change_addons"
)

type SubscriptionEntitlement

type SubscriptionEntitlement struct {
	Entitlement entitlement.Entitlement
	Cadence     models.CadencedModel
}

func (SubscriptionEntitlement) ToScheduleSubscriptionEntitlementInput

func (s SubscriptionEntitlement) ToScheduleSubscriptionEntitlementInput() ScheduleSubscriptionEntitlementInput

func (SubscriptionEntitlement) Validate

func (s SubscriptionEntitlement) Validate() error

type SubscriptionItem

type SubscriptionItem struct {
	models.NamespacedID  `json:",inline"`
	models.ManagedModel  `json:",inline"`
	models.MetadataModel `json:",inline"`

	Annotations models.Annotations `json:"annotations"`

	// SubscriptionItem doesn't have a separate Cadence, only one relative to the phase, denoting if it's intentionally different from the phase's cadence.
	// The durations are relative to phase start.
	ActiveFromOverrideRelativeToPhaseStart *datetime.ISODuration `json:"activeFromOverrideRelativeToPhaseStart,omitempty"`
	ActiveToOverrideRelativeToPhaseStart   *datetime.ISODuration `json:"activeToOverrideRelativeToPhaseStart,omitempty"`

	// The defacto cadence of the item is calculated and persisted after each change.
	models.CadencedModel `json:",inline"`

	BillingBehaviorOverride BillingBehaviorOverride `json:"billingBehaviorOverride"`

	// SubscriptionID is the ID of the subscription this item belongs to.
	SubscriptionId string `json:"subscriptionId"`
	// PhaseID is the ID of the phase this item belongs to.
	PhaseId string `json:"phaseId"`
	// Key is the unique key of the item in the phase.
	Key string `json:"itemKey"`

	RateCard productcatalog.RateCard `json:"rateCard"`

	EntitlementID *string `json:"entitlementId,omitempty"`
	// Name
	Name string `json:"name"`

	// Description
	Description *string `json:"description,omitempty"`
}

func (SubscriptionItem) AsEntityInput

func (SubscriptionItem) GetCadence

func (i SubscriptionItem) GetCadence(phaseCadence models.CadencedModel) models.CadencedModel

func (*SubscriptionItem) UnmarshalJSON

func (i *SubscriptionItem) UnmarshalJSON(b []byte) error

type SubscriptionItemRef

type SubscriptionItemRef struct {
	SubscriptionId string `json:"subscriptionId"`
	PhaseKey       string `json:"phaseKey"`
	ItemKey        string `json:"itemKey"`
}

SubscriptionItemRef is an unstable reference to a SubscriptionItem

func (SubscriptionItemRef) Equals

type SubscriptionItemRepository

type SubscriptionItemRepository interface {
	entutils.TxCreator

	GetForSubscriptionAt(ctx context.Context, subscriptionID models.NamespacedID, at time.Time) ([]SubscriptionItem, error)

	Create(ctx context.Context, input CreateSubscriptionItemEntityInput) (SubscriptionItem, error)
	Delete(ctx context.Context, id models.NamespacedID) error
	GetByID(ctx context.Context, id models.NamespacedID) (SubscriptionItem, error)
}

type SubscriptionItemSpec

type SubscriptionItemSpec struct {
	CreateSubscriptionItemInput `json:",inline"`
}

func (SubscriptionItemSpec) GetCadence

func (s SubscriptionItemSpec) GetCadence(phaseCadence models.CadencedModel) models.CadencedModel

func (SubscriptionItemSpec) GetFullServicePeriodAt

func (s SubscriptionItemSpec) GetFullServicePeriodAt(
	inp GetFullServicePeriodAtInput,
) (timeutil.ClosedPeriod, error)

GetFullServicePeriodAt returns the full service period for an item at a given time To get the de-facto service period, use the intersection of the item's activity with the returned period.

func (SubscriptionItemSpec) GetRef

func (*SubscriptionItemSpec) SyncAnnotations

func (s *SubscriptionItemSpec) SyncAnnotations() error

func (SubscriptionItemSpec) ToCreateSubscriptionItemEntityInput

func (s SubscriptionItemSpec) ToCreateSubscriptionItemEntityInput(
	phaseID models.NamespacedID,
	phaseCadence models.CadencedModel,
	entitlement *entitlement.Entitlement,
) (CreateSubscriptionItemEntityInput, error)

func (*SubscriptionItemSpec) Validate

func (s *SubscriptionItemSpec) Validate() error

type SubscriptionItemView

type SubscriptionItemView struct {
	SubscriptionItem SubscriptionItem     `json:"subscriptionItem"`
	Spec             SubscriptionItemSpec `json:"spec"`

	Entitlement *SubscriptionEntitlement `json:"entitlement,omitempty"`
	Feature     *feature.Feature         `json:"feature,omitempty"`
}

func (*SubscriptionItemView) AsSpec

func (*SubscriptionItemView) Validate

func (s *SubscriptionItemView) Validate() error

type SubscriptionList

type SubscriptionList = pagination.PagedResponse[Subscription]

type SubscriptionNotFoundError

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

SubscriptionNotFoundError is returned when a meter is not found.

func (*SubscriptionNotFoundError) Error

func (e *SubscriptionNotFoundError) Error() string

Error returns the error message.

func (*SubscriptionNotFoundError) Unwrap

func (e *SubscriptionNotFoundError) Unwrap() error

Unwrap returns the wrapped error.

type SubscriptionPhase

type SubscriptionPhase struct {
	models.NamespacedID  `json:",inline"`
	models.ManagedModel  `json:",inline"`
	models.MetadataModel `json:",inline"`

	ActiveFrom time.Time `json:"activeFrom"`

	// SubscriptionID is the ID of the subscription this phase belongs to.
	SubscriptionID string `json:"subscriptionId"`

	// Key is the unique key for Phase.
	Key string `json:"key"`

	// Name
	Name string `json:"name"`

	// Description
	Description *string `json:"description,omitempty"`

	// SortHint
	SortHint *uint8 `json:"sortHint,omitempty"`
}

type SubscriptionPhaseRepository

type SubscriptionPhaseRepository interface {
	entutils.TxCreator

	// Returns the phases for a subscription
	GetForSubscriptionAt(ctx context.Context, subscriptionID models.NamespacedID, at time.Time) ([]SubscriptionPhase, error)

	// Create a new subscription phase
	Create(ctx context.Context, input CreateSubscriptionPhaseEntityInput) (SubscriptionPhase, error)
	Delete(ctx context.Context, id models.NamespacedID) error
}

type SubscriptionPhaseSpec

type SubscriptionPhaseSpec struct {
	// Duration is not part of the Spec by design
	CreateSubscriptionPhasePlanInput     `json:",inline"`
	CreateSubscriptionPhaseCustomerInput `json:",inline"`

	// In each key, for each phase, we have a list of item specs to account for mid-phase changes
	ItemsByKey map[string][]*SubscriptionItemSpec `json:"itemsByKey"`
}

func (SubscriptionPhaseSpec) GetBillableItemsByKey

func (s SubscriptionPhaseSpec) GetBillableItemsByKey() map[string][]*SubscriptionItemSpec

GetBillableItemsByKey returns a map of billable items by key

func (SubscriptionPhaseSpec) HasBillables

func (s SubscriptionPhaseSpec) HasBillables() bool

func (SubscriptionPhaseSpec) HasEntitlements

func (s SubscriptionPhaseSpec) HasEntitlements() bool

func (SubscriptionPhaseSpec) HasMeteredBillables

func (s SubscriptionPhaseSpec) HasMeteredBillables() bool

func (SubscriptionPhaseSpec) SyncAnnotations

func (s SubscriptionPhaseSpec) SyncAnnotations() error

func (SubscriptionPhaseSpec) ToCreateSubscriptionPhaseEntityInput

func (s SubscriptionPhaseSpec) ToCreateSubscriptionPhaseEntityInput(
	subscription Subscription,
	activeFrom time.Time,
) CreateSubscriptionPhaseEntityInput

func (SubscriptionPhaseSpec) Validate

func (s SubscriptionPhaseSpec) Validate(
	phaseCadence models.CadencedModel,
) error

type SubscriptionPhaseView

type SubscriptionPhaseView struct {
	SubscriptionPhase SubscriptionPhase                 `json:"subscriptionPhase"`
	Spec              SubscriptionPhaseSpec             `json:"spec"`
	ItemsByKey        map[string][]SubscriptionItemView `json:"itemsByKey"`
}

func (*SubscriptionPhaseView) AsSpec

func (*SubscriptionPhaseView) Validate

func (s *SubscriptionPhaseView) Validate(includeItems bool) error

type SubscriptionRepository

type SubscriptionRepository interface {
	entutils.TxCreator

	models.CadencedResourceRepo[Subscription]

	// Returns all subscriptions active or scheduled after the given timestamp
	GetAllForCustomerSince(ctx context.Context, customerID models.NamespacedID, at time.Time) ([]Subscription, error)

	// Returns the subscription by ID
	GetByID(ctx context.Context, subscriptionID models.NamespacedID) (Subscription, error)

	// Create a new subscription
	Create(ctx context.Context, input CreateSubscriptionEntityInput) (Subscription, error)

	// Delete a subscription
	Delete(ctx context.Context, id models.NamespacedID) error

	// List subscriptions
	List(ctx context.Context, params ListSubscriptionsInput) (SubscriptionList, error)
}

type SubscriptionSpec

type SubscriptionSpec struct {
	CreateSubscriptionPlanInput     `json:",inline"`
	CreateSubscriptionCustomerInput `json:",inline"`

	// We use pointers so Patches can manipulate the spec
	Phases map[string]*SubscriptionPhaseSpec `json:"phases"`
}

func NewSpecFromPlan

NewSpecFromPlan creates a SubscriptionSpec from a Plan and a CreateSubscriptionCustomerInput.

func (*SubscriptionSpec) Apply

func (s *SubscriptionSpec) Apply(applies AppliesToSpec, context ApplyContext) error

func (*SubscriptionSpec) ApplyMany

func (s *SubscriptionSpec) ApplyMany(applieses []AppliesToSpec, aCtx ApplyContext) error

func (*SubscriptionSpec) GetAlignedBillingPeriodAt

func (s *SubscriptionSpec) GetAlignedBillingPeriodAt(at time.Time) (timeutil.ClosedPeriod, error)

For a phase in an Aligned subscription, there's a single aligned BillingPeriod for all items in that phase. The period starts with the phase and iterates every subscription.BillingCadence duration, but can be reanchored to the time of an edit.

func (*SubscriptionSpec) GetCurrentPhaseAt

func (s *SubscriptionSpec) GetCurrentPhaseAt(t time.Time) (*SubscriptionPhaseSpec, bool)

func (*SubscriptionSpec) GetPhaseCadence

func (s *SubscriptionSpec) GetPhaseCadence(phaseKey string) (models.CadencedModel, error)

func (*SubscriptionSpec) GetSortedPhases

func (s *SubscriptionSpec) GetSortedPhases() []*SubscriptionPhaseSpec

GetSortedPhases returns the subscription phase references time sorted order ASC.

func (*SubscriptionSpec) HasBillables

func (s *SubscriptionSpec) HasBillables() bool

func (*SubscriptionSpec) HasEntitlements

func (s *SubscriptionSpec) HasEntitlements() bool

func (*SubscriptionSpec) HasMeteredBillables

func (s *SubscriptionSpec) HasMeteredBillables() bool

func (*SubscriptionSpec) SyncAnnotations

func (s *SubscriptionSpec) SyncAnnotations() error

SyncAnnotations serves as a central place where we can calculate annotation default for the Subscription contents

func (*SubscriptionSpec) ToCreateSubscriptionEntityInput

func (s *SubscriptionSpec) ToCreateSubscriptionEntityInput(ns string) CreateSubscriptionEntityInput

func (*SubscriptionSpec) Validate

func (s *SubscriptionSpec) Validate() error

func (*SubscriptionSpec) ValidateAlignment

func (s *SubscriptionSpec) ValidateAlignment() error

type SubscriptionStateMachine

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

SubscriptionStateMachine is a very simple state machine that determines what actions can be taken on a Subscription

func (SubscriptionStateMachine) CanTransitionOrErr

func (sm SubscriptionStateMachine) CanTransitionOrErr(ctx context.Context, action SubscriptionAction) error

type SubscriptionStatus

type SubscriptionStatus string
const (
	// Active means the subscription is active and the customer is being billed
	SubscriptionStatusActive SubscriptionStatus = "active"
	// Canceled means the subscription has already been canceled but is still active
	SubscriptionStatusCanceled SubscriptionStatus = "canceled"
	// Inactive means the subscription is inactive (might have been previously active) and the customer is not being billed
	SubscriptionStatusInactive SubscriptionStatus = "inactive"
	// Scheduled means the subscription is scheduled to be active in the future
	SubscriptionStatusScheduled SubscriptionStatus = "scheduled"
)

func (SubscriptionStatus) Validate

func (s SubscriptionStatus) Validate() error

type SubscriptionValidator

type SubscriptionValidator interface {
	ValidateCreate(context.Context, SubscriptionView) error
	ValidateUpdate(context.Context, SubscriptionView) error
	ValidateCancel(context.Context, SubscriptionView) error
	ValidateContinue(context.Context, SubscriptionView) error
	ValidateDelete(context.Context, SubscriptionView) error
}

type SubscriptionView

type SubscriptionView struct {
	Subscription Subscription            `json:"subscription"`
	Customer     customer.Customer       `json:"customer"`
	Spec         SubscriptionSpec        `json:"spec"`
	Phases       []SubscriptionPhaseView `json:"phases"`
}

func NewSubscriptionView

func NewSubscriptionView(
	sub Subscription,
	cust customer.Customer,
	phases []SubscriptionPhase,
	items []SubscriptionItem,
	ents []SubscriptionEntitlement,
	entFeats []feature.Feature,
	itemFeats []feature.Feature,
) (*SubscriptionView, error)

func (SubscriptionView) AsSpec

func (SubscriptionView) GetPhaseByKey

func (s SubscriptionView) GetPhaseByKey(key string) (*SubscriptionPhaseView, bool)

func (*SubscriptionView) Validate

func (s *SubscriptionView) Validate(includePhases bool) error

type Timing

type Timing struct {
	Custom *time.Time
	Enum   *TimingEnum
}

Timing represents the timing of a change in a subscription, such as a plan change or a cancellation.

func (Timing) Resolve

func (c Timing) Resolve() (time.Time, error)

func (Timing) ResolveForSpec

func (c Timing) ResolveForSpec(spec SubscriptionSpec) (time.Time, error)

func (Timing) Validate

func (c Timing) Validate() error

func (Timing) ValidateForAction

func (c Timing) ValidateForAction(action SubscriptionAction, subView *SubscriptionView) error

type TimingEnum

type TimingEnum string
const (
	// Immediate means the change will take effect immediately.
	TimingImmediate TimingEnum = "immediate"
	// NextBillingCycle means the change will take effect at the start of the next billing cycle.
	// This value is only supported for aligned subscriptions.
	TimingNextBillingCycle TimingEnum = "next_billing_cycle"
)

func (TimingEnum) Validate

func (c TimingEnum) Validate() error

type ToScheduleSubscriptionEntitlementInputOptions

type ToScheduleSubscriptionEntitlementInputOptions struct {
	Customer             customer.Customer
	Cadence              models.CadencedModel
	PhaseStart           time.Time
	AlignedBillingAnchor time.Time
}

type UpdatedEvent

type UpdatedEvent struct {
	// We can consider adding the old version or diff here if needed
	UpdatedView SubscriptionView `json:"updatedView"`
	UserID      *string          `json:"userId,omitempty"`
}

func NewUpdatedEvent

func NewUpdatedEvent(ctx context.Context, view SubscriptionView) UpdatedEvent

NewUpdatedEvent creates a new updated event

func (UpdatedEvent) EventMetadata

func (s UpdatedEvent) EventMetadata() metadata.EventMetadata

func (UpdatedEvent) EventName

func (s UpdatedEvent) EventName() string

func (UpdatedEvent) Validate

func (s UpdatedEvent) Validate() error

type ValidatorService

type ValidatorService interface {
	RegisterValidator(SubscriptionValidator) error
}

type ValuePatch

type ValuePatch[T any] interface {
	Patch
	Value() T
	AnyValuePatch
}

Directories

Path Synopsis
validators

Jump to

Keyboard shortcuts

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