tariff

package
v0.0.0-...-9204231 Latest Latest
Warning

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

Go to latest
Published: Jul 11, 2026 License: AGPL-3.0 Imports: 29 Imported by: 0

Documentation

Overview

Package tariff implements the tariff-manager built-in plugin. All tariff logic is self-contained here — the platform only calls Plugin() and RegisterRoutes().

Index

Constants

View Source
const (
	EventTariffCreated       = "tariff.created"
	EventTariffUpdated       = "tariff.updated"
	EventTariffArchived      = "tariff.archived"
	EventTariffStockDepleted = "tariff.stock_depleted"
	EventTariffPublished     = "tariff.published"
)

Tariff lifecycle events.

View Source
const (
	EventPricingRuleApplied   = "pricing.rule_applied"
	EventPricingModifierFired = "pricing.modifier_fired"
)

Pricing events.

View Source
const (
	EventPromoCodeCreated     = "promo.code_created"
	EventPromoCodeUsed        = "promo.code_used"
	EventPromoCodeExhausted   = "promo.code_exhausted"
	EventPromoCampaignStarted = "promo.campaign_started"
	EventPromoCampaignEnded   = "promo.campaign_ended"
)

Promo code events.

View Source
const (
	EventABExperimentStarted   = "ab.experiment_started"
	EventABVariantAssigned     = "ab.variant_assigned"
	EventABExperimentConcluded = "ab.experiment_concluded"
)

A/B experiment events.

View Source
const (
	EventCohortUserAdded    = "cohort.user_added"
	EventCohortUserRemoved  = "cohort.user_removed"
	EventCohortRecalculated = "cohort.recalculated"
)

Cohort events.

View Source
const (
	HookCalculatePrice      = "tariff.calculate_price"
	HookValidateEligibility = "tariff.validate_eligibility"
	HookListVisible         = "tariff.list_visible"
	HookPromoValidate       = "promo.validate"
	HookPromoApply          = "promo.apply"
	HookUserResolveGroups   = "user.resolve_groups"
	HookUserResolveTier     = "user.resolve_tier"
)

Sync hook names — for use with hook dispatcher.

View Source
const (
	HookTariffPurchased   = "tariff.purchased"
	HookTariffExpired     = "tariff.expired"
	HookPromoCodeUsed     = "promo.code_used"
	HookCohortUserAdded   = "cohort.user_added"
	HookABVariantAssigned = "ab.variant_assigned"
)

Async hook names.

View Source
const (
	// PluginSlug is the canonical slug for the tariff-manager plugin.
	PluginSlug = "tariff-manager"

	// CollectionName is the plugin collection where tariffs are stored.
	CollectionName = "tariffs"
)
View Source
const (
	CollectionPricingRules    = "pricing_rules"
	CollectionPromoCodes      = "promo_codes"
	CollectionABExperiments   = "ab_experiments"
	CollectionCohorts         = "cohorts"
	CollectionAudienceRules   = "audience_rules"
	CollectionPurchaseHistory = "purchase_history"
)
View Source
const (
	AudienceAll      = "all"
	AudienceB2C      = "b2c"
	AudienceB2B      = "b2b"
	AudienceReseller = "reseller"
)
View Source
const (
	UpgradePolicyImmediate  = "immediate"
	UpgradePolicyNextPeriod = "next_period"
	UpgradePolicyProrated   = "prorated"

	DowngradePolicyNextPeriod      = "next_period"
	DowngradePolicyImmediateRefund = "immediate_refund"

	CancellationPolicyImmediate   = "immediate"
	CancellationPolicyEndOfPeriod = "end_of_period"

	CharmStrategyNone = "none"
	CharmStrategy99   = "99"
	CharmStrategy95   = "95"

	OverageRoundingGB    = "gb"
	OverageRoundingMB    = "mb"
	OverageRounding100MB = "100mb"

	// Predefined period labels
	PeriodLabelMonth   = "1 month"
	PeriodLabel3Months = "3 months"
	PeriodLabel6Months = "6 months"
	PeriodLabelYear    = "1 year"

	TrafficResetNoReset      = "NO_RESET"
	TrafficResetDay          = "DAY"
	TrafficResetWeek         = "WEEK"
	TrafficResetMonth        = "MONTH"
	TrafficResetMonthRolling = "MONTH_ROLLING"
)

Variables

This section is empty.

Functions

func DerivePlanID

func DerivePlanID(docID string, durationDays int, hasPricingPeriods bool) (string, error)

DerivePlanID computes the billing PlanID for one period of a tariff.

For single-period tariffs (hasPricingPeriods == false) the PlanID equals the tariff document ID directly. For multi-period tariffs (hasPricingPeriods == true) the PlanID is a deterministic UUIDv5 derived from the document ID and the period duration in days.

The derivation is the canonical source of truth shared by syncTariffToPlan and the tariff reader: plans.list PlanIDs MUST equal the PlanIDs checkout resolves, so both sides must call this function — never inline the logic.

func Plugin

func Plugin() plugin.BuiltInPluginDef

Plugin returns the built-in plugin definition for the tariff-manager.

func RegisterRoutes

func RegisterRoutes(registry *gateway.BuiltinRouteRegistry, h *Handler)

RegisterRoutes maps the plugin's manifest route functions to native Go handlers.

func SupportedCurrencies

func SupportedCurrencies() []string

SupportedCurrencies returns the list of supported currency codes.

Types

type ABExperimentInput

type ABExperimentInput struct {
	Name               string               `json:"name"`
	TariffID           string               `json:"tariff_id"`
	Status             string               `json:"status"` // "draft" | "running" | "paused" | "completed" | "archived"
	Variants           []ABVariant          `json:"variants"`
	AssignmentStrategy string               `json:"assignment_strategy"` // "hash_user_id" | "hash_tenant_id" | "random"
	StartedAt          string               `json:"started_at,omitempty"`
	EndedAt            string               `json:"ended_at,omitempty"`
	Metrics            map[string]ABMetrics `json:"metrics,omitempty"`
	StatSignificance   float64              `json:"stat_significance"`
	Winner             string               `json:"winner,omitempty"`
	StopRule           string               `json:"stop_rule"`
}

ABExperimentInput is the request body for creating or updating an A/B experiment.

type ABExperimentResponse

type ABExperimentResponse struct {
	ID string `json:"id"`
	ABExperimentInput
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

ABExperimentResponse wraps ABExperimentInput with server-assigned fields.

type ABMetrics

type ABMetrics struct {
	Exposures    int   `json:"exposures"`
	Conversions  int   `json:"conversions"`
	RevenueCents int64 `json:"revenue_cents"`
}

ABMetrics tracks conversion data for a single variant.

type ABVariant

type ABVariant struct {
	Key        string `json:"key"`
	PriceCents int64  `json:"price_cents"`
	TrafficPct int    `json:"traffic_pct"` // basis points, total must = 10000
}

ABVariant is a single price variant in an experiment.

type ABVariantAssignedPayload

type ABVariantAssignedPayload struct {
	ExperimentID string `json:"experiment_id"`
	UserID       string `json:"user_id"`
	VariantKey   string `json:"variant_key"`
	PriceCents   int64  `json:"price_cents"`
}

ABVariantAssignedPayload is emitted for observability when a user is assigned to an experiment variant.

type BasePrice

type BasePrice struct {
	Currency    string `json:"currency"`
	AmountCents int64  `json:"amount_cents"`
}

BasePrice is a single base price entry in a specific currency.

type BillingModel

type BillingModel string

BillingModel defines how a tariff charges the customer.

const (
	BillingModelFixed     BillingModel = "fixed"
	BillingModelRecurring BillingModel = "recurring"
	BillingModelLifetime  BillingModel = "lifetime"
	BillingModelPAYG      BillingModel = "payg"
	BillingModelCredit    BillingModel = "credit"
	BillingModelHybrid    BillingModel = "hybrid"
)

func (BillingModel) IsValid

func (b BillingModel) IsValid() bool

IsValid returns true if b is one of the known billing models.

type CohortInput

type CohortInput struct {
	Name            string          `json:"name"`
	Selector        CohortSelector  `json:"selector"`
	PricingOverride PricingOverride `json:"pricing_override"`
	UserCount       int             `json:"user_count"`
}

CohortInput is the request body for creating or updating a cohort.

type CohortResponse

type CohortResponse struct {
	ID string `json:"id"`
	CohortInput
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

CohortResponse wraps CohortInput with server-assigned identity and timestamps.

type CohortRule

type CohortRule struct {
	Event      string `json:"event,omitempty"` // e.g. "SubCancelled"
	WithinDays int    `json:"within_days,omitempty"`
	UserAttr   string `json:"user_attr,omitempty"` // e.g. "lifetime_months"
	Gte        int    `json:"gte,omitempty"`
	Lte        int    `json:"lte,omitempty"`
	Eq         string `json:"eq,omitempty"`
}

CohortRule is a single selection rule within a CohortSelector.

type CohortSelector

type CohortSelector struct {
	Type  string       `json:"type"` // "event_based" | "attribute_based" | "manual"
	Rules []CohortRule `json:"rules"`
}

CohortSelector defines how users are selected into a cohort.

type DiscountSpec

type DiscountSpec struct {
	Type             string `json:"type"`               // "percent_off" | "fixed_off"
	Value            int64  `json:"value"`              // basis points for %, cents for fixed
	MaxDiscountCents int64  `json:"max_discount_cents"` // cap on discount amount
}

DiscountSpec defines the discount type and value.

type EligibilityRules

type EligibilityRules struct {
	RequirePromoGroups   []string `json:"require_promo_groups,omitempty"`
	RequireAnyOfGroups   []string `json:"require_any_of_groups,omitempty"`
	ExcludeGroups        []string `json:"exclude_groups,omitempty"`
	RequireTiers         []string `json:"require_tiers,omitempty"`
	MinLifetimeMonths    int      `json:"min_lifetime_months,omitempty"`
	RequireVerifiedEmail bool     `json:"require_verified_email,omitempty"`
	RequireKYC           bool     `json:"require_kyc,omitempty"`
	CustomRuleIDs        []string `json:"custom_rule_ids,omitempty"`
}

EligibilityRules captures constraints that determine whether a user is eligible to purchase a tariff.

type Handler

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

Handler provides HTTP endpoints for tariff CRUD, pricing, promo codes, A/B experiments, cohorts, analytics, and Remnawave data lookups.

func NewHandler

func NewHandler(collections pluginstore.Store, pluginRepo plugin.PluginRepository, planRepo billing.PlanRepository, logger *slog.Logger) *Handler

NewHandler creates a tariff Handler.

func (*Handler) ArchiveTariff

func (h *Handler) ArchiveTariff(w http.ResponseWriter, r *http.Request)

ArchiveTariff soft-deletes a tariff by setting is_active=false.

func (*Handler) BulkGeneratePromoCodes

func (h *Handler) BulkGeneratePromoCodes(w http.ResponseWriter, r *http.Request)

BulkGeneratePromoCodes generates N unique promo codes with a shared template.

func (*Handler) CloneTariff

func (h *Handler) CloneTariff(w http.ResponseWriter, r *http.Request)

CloneTariff duplicates a tariff with a new ID.

func (*Handler) ConcludeABExperiment

func (h *Handler) ConcludeABExperiment(w http.ResponseWriter, r *http.Request)

ConcludeABExperiment marks an experiment as completed with a winner.

func (*Handler) CreateABExperiment

func (h *Handler) CreateABExperiment(w http.ResponseWriter, r *http.Request)

CreateABExperiment creates a new A/B experiment.

func (*Handler) CreateCohort

func (h *Handler) CreateCohort(w http.ResponseWriter, r *http.Request)

CreateCohort decodes, validates, and inserts a new cohort.

func (*Handler) CreatePricingRule

func (h *Handler) CreatePricingRule(w http.ResponseWriter, r *http.Request)

CreatePricingRule creates a new pricing rule.

func (*Handler) CreatePromoCode

func (h *Handler) CreatePromoCode(w http.ResponseWriter, r *http.Request)

CreatePromoCode creates a new promo code.

func (*Handler) CreateTariff

func (h *Handler) CreateTariff(w http.ResponseWriter, r *http.Request)

func (*Handler) CustomizeReseller

func (h *Handler) CustomizeReseller(w http.ResponseWriter, r *http.Request)

func (*Handler) DeleteCohort

func (h *Handler) DeleteCohort(w http.ResponseWriter, r *http.Request)

DeleteCohort removes a cohort by ID.

func (*Handler) DeletePricingRule

func (h *Handler) DeletePricingRule(w http.ResponseWriter, r *http.Request)

DeletePricingRule removes a pricing rule by ID.

func (*Handler) DeletePromoCode

func (h *Handler) DeletePromoCode(w http.ResponseWriter, r *http.Request)

DeletePromoCode removes a promo code by ID.

func (*Handler) DeleteTariff

func (h *Handler) DeleteTariff(w http.ResponseWriter, r *http.Request)

DeleteTariff deletes a tariff document and deactivates linked billing plans.

func (*Handler) ExportTariffs

func (h *Handler) ExportTariffs(w http.ResponseWriter, r *http.Request)

ExportTariffs exports all tariffs as JSON.

func (*Handler) GetABExperimentResults

func (h *Handler) GetABExperimentResults(w http.ResponseWriter, r *http.Request)

GetABExperimentResults returns experiment data + variant assignment for a test user.

func (*Handler) GetCohort

func (h *Handler) GetCohort(w http.ResponseWriter, r *http.Request)

GetCohort returns a single cohort by its ID.

func (*Handler) GetCohortUsers

func (h *Handler) GetCohortUsers(w http.ResponseWriter, r *http.Request)

GetCohortUsers returns a placeholder response for the cohort's user membership. Actual cohort membership is recalculated asynchronously.

func (*Handler) GetConversionFunnel

func (h *Handler) GetConversionFunnel(w http.ResponseWriter, r *http.Request)

func (*Handler) GetMRRByTariff

func (h *Handler) GetMRRByTariff(w http.ResponseWriter, r *http.Request)

func (*Handler) GetPricingRule

func (h *Handler) GetPricingRule(w http.ResponseWriter, r *http.Request)

GetPricingRule returns a single pricing rule by ID.

func (*Handler) GetPromoCode

func (h *Handler) GetPromoCode(w http.ResponseWriter, r *http.Request)

GetPromoCode returns a single promo code by ID.

func (*Handler) GetPromoCodeStats

func (h *Handler) GetPromoCodeStats(w http.ResponseWriter, r *http.Request)

GetPromoCodeStats returns usage statistics for a promo code.

func (*Handler) GetResellerCatalog

func (h *Handler) GetResellerCatalog(w http.ResponseWriter, r *http.Request)

func (*Handler) GetTariff

func (h *Handler) GetTariff(w http.ResponseWriter, r *http.Request)

func (*Handler) GetTariffByPlanID

func (h *Handler) GetTariffByPlanID(ctx context.Context, planID string) (*TariffResponse, error)

GetTariffByPlanID resolves a planID to a TariffResponse by scanning active telegram-visible tariffs. planID may be either a tariff document ID (single-period tariff) or a derived UUIDv5 (one period of a multi-period tariff).

Returns pluginstore.ErrDocumentNotFound (wrapped) when no matching tariff is found. ctx is forwarded to ListVisibleTariffs without modification.

func (*Handler) GetTariffPrice

func (h *Handler) GetTariffPrice(w http.ResponseWriter, r *http.Request)

GetTariffPrice calculates the personalized price for a tariff.

func (*Handler) GetTariffStats

func (h *Handler) GetTariffStats(w http.ResponseWriter, r *http.Request)

GetTariffStats returns basic sales stats for a tariff (placeholder).

func (*Handler) GetTariffVersionHistory

func (h *Handler) GetTariffVersionHistory(w http.ResponseWriter, r *http.Request)

GetTariffVersionHistory returns change history for a tariff (placeholder).

func (*Handler) ImportTariffs

func (h *Handler) ImportTariffs(w http.ResponseWriter, r *http.Request)

ImportTariffs imports tariffs from a JSON array.

func (*Handler) ListABExperiments

func (h *Handler) ListABExperiments(w http.ResponseWriter, r *http.Request)

ListABExperiments returns all A/B experiments.

func (*Handler) ListCohorts

func (h *Handler) ListCohorts(w http.ResponseWriter, r *http.Request)

ListCohorts returns all cohorts in the CollectionCohorts collection.

func (*Handler) ListExternalSquads

func (h *Handler) ListExternalSquads(w http.ResponseWriter, r *http.Request)

func (*Handler) ListInternalSquads

func (h *Handler) ListInternalSquads(w http.ResponseWriter, r *http.Request)

func (*Handler) ListNodes

func (h *Handler) ListNodes(w http.ResponseWriter, r *http.Request)

func (*Handler) ListPanelsForTariff

func (h *Handler) ListPanelsForTariff(w http.ResponseWriter, r *http.Request)

ListPanelsForTariff returns panel connections for tariff form dropdown.

func (*Handler) ListPricingRules

func (h *Handler) ListPricingRules(w http.ResponseWriter, r *http.Request)

ListPricingRules returns all pricing rules.

func (*Handler) ListPromoCodes

func (h *Handler) ListPromoCodes(w http.ResponseWriter, r *http.Request)

ListPromoCodes returns all promo codes.

func (*Handler) ListTariffCatalog

func (h *Handler) ListTariffCatalog(w http.ResponseWriter, r *http.Request)

ListTariffCatalog returns tariffs grouped and sorted for storefront display.

func (*Handler) ListTariffs

func (h *Handler) ListTariffs(w http.ResponseWriter, r *http.Request)

func (*Handler) ListVisibleTariffs

func (h *Handler) ListVisibleTariffs(ctx context.Context, channel string) ([]TariffResponse, error)

ListVisibleTariffs returns the active tariffs visible in the given channel for the tenant encoded in ctx.

ctx must carry the tenant GUC — the caller (e.g. a bot op wrapped in RunInTx(WithTenantID(...))) owns that contract; this method does NOT set the GUC itself.

Channel mapping:

  • "telegram" → VisibleInTelegram
  • "cabinet" → VisibleInCabinet
  • "public" → VisibleInPublic
  • unknown → VisibleInTelegram (default)

func (*Handler) PauseABExperiment

func (h *Handler) PauseABExperiment(w http.ResponseWriter, r *http.Request)

PauseABExperiment transitions a running experiment to paused.

func (*Handler) SimulatePricingRule

func (h *Handler) SimulatePricingRule(w http.ResponseWriter, r *http.Request)

SimulatePricingRule performs a dry-run pricing calculation using the rule's modifiers.

func (*Handler) StartABExperiment

func (h *Handler) StartABExperiment(w http.ResponseWriter, r *http.Request)

StartABExperiment transitions an experiment from draft/paused to running.

func (*Handler) SyncAllTariffs

func (h *Handler) SyncAllTariffs(w http.ResponseWriter, r *http.Request)

SyncAllTariffs forces a sync of all existing tariffs to billing Plans. Returns detailed results including errors for debugging.

func (*Handler) UpdateCohort

func (h *Handler) UpdateCohort(w http.ResponseWriter, r *http.Request)

UpdateCohort updates an existing cohort by ID.

func (*Handler) UpdatePricingRule

func (h *Handler) UpdatePricingRule(w http.ResponseWriter, r *http.Request)

UpdatePricingRule updates an existing pricing rule by ID.

func (*Handler) UpdatePromoCode

func (h *Handler) UpdatePromoCode(w http.ResponseWriter, r *http.Request)

UpdatePromoCode updates an existing promo code by ID.

func (*Handler) UpdateTariff

func (h *Handler) UpdateTariff(w http.ResponseWriter, r *http.Request)

func (*Handler) ValidatePromoCode

func (h *Handler) ValidatePromoCode(w http.ResponseWriter, r *http.Request)

ValidatePromoCode checks whether a promo code is valid without applying it.

type ModifierAction

type ModifierAction struct {
	Op    ModifierOp `json:"op"`
	Value int64      `json:"value"` // basis points for percent (1000=10%), cents for fixed
}

ModifierAction describes the arithmetic to apply.

type ModifierOp

type ModifierOp string

ModifierOp enumerates the operations a pricing modifier can perform.

const (
	ModifierPercentOff   ModifierOp = "percent_off"
	ModifierPercentUp    ModifierOp = "percent_up"
	ModifierFixedOff     ModifierOp = "fixed_off"
	ModifierFixedUp      ModifierOp = "fixed_up"
	ModifierReplacePrice ModifierOp = "replace_price"
	ModifierMultiply     ModifierOp = "multiply"
)

type PriceContext

type PriceContext struct {
	TariffID string `json:"tariff_id"`
	UserID   string `json:"user_id"`
	TenantID string `json:"tenant_id"`
	Country  string `json:"country"` // ISO-3166
	Currency string `json:"currency"`
	Duration int    `json:"duration"` // days
	Quantity int    `json:"quantity"` // seats (B2B)

	PromoCode string `json:"promo_code,omitempty"`

	// Extension cache: populated by early hooks, read by later stages.
	UserGroups []string `json:"user_groups,omitempty"`
	UserCohort string   `json:"user_cohort,omitempty"`
	UserTier   string   `json:"user_tier,omitempty"`

	BasePrice  int64               `json:"base_price"`
	Modifiers  []PriceModification `json:"modifiers"`
	FinalPrice int64               `json:"final_price"`
	Breakdown  map[string]int64    `json:"breakdown"`
}

PriceContext flows through the pricing pipeline, accumulating modifications and producing a final price with full audit trail.

type PriceModification

type PriceModification struct {
	Step        string `json:"step"`
	RuleID      string `json:"rule_id,omitempty"`
	AmountCents int64  `json:"amount_cents"`
	Reason      string `json:"reason"`
}

PriceModification is a single audit entry in the pricing pipeline.

type PricingOverride

type PricingOverride struct {
	PricingRuleID string `json:"pricing_rule_id,omitempty"`
	PromoCodeAuto string `json:"promo_code_auto,omitempty"` // auto-apply promo
}

PricingOverride links a cohort to a specific pricing rule or auto-applied promo code.

type PricingPeriod

type PricingPeriod struct {
	DurationDays int    `json:"duration_days"`
	PriceAmount  int64  `json:"price_amount"` // in cents
	Label        string `json:"label"`        // "1 month", "3 months", "1 year"
	SavePercent  int    `json:"save_percent"` // 0, 10, 17 — discount vs monthly
	IsDefault    bool   `json:"is_default"`   // highlighted in UI
}

PricingPeriod represents one subscription period option for a tariff. A tariff can offer multiple periods (e.g., 1 month, 3 months, 1 year) each with its own price and optional discount.

type PricingPipeline

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

PricingPipeline orchestrates a sequence of pricing stages.

func DefaultPipeline

func DefaultPipeline(tariff *TariffInput, rule *PricingRuleInput, promo *PromoCodeInput) *PricingPipeline

DefaultPipeline creates the standard 8-stage pricing pipeline.

func NewPricingPipeline

func NewPricingPipeline(stages ...PricingStage) *PricingPipeline

NewPricingPipeline creates a pipeline from the given stages.

func (*PricingPipeline) Calculate

func (p *PricingPipeline) Calculate(ctx *PriceContext) error

Calculate runs every stage and ensures the final price is non-negative.

type PricingRuleInput

type PricingRuleInput struct {
	Name            string                `json:"name"`
	BasePrices      []BasePrice           `json:"base_prices"`
	Modifiers       []PricingRuleModifier `json:"modifiers"`
	StackingPolicy  string                `json:"stacking_policy"` // "additive" | "multiplicative" | "best_only"
	PriceFloorCents int64                 `json:"price_floor_cents"`
	Rounding        string                `json:"rounding"` // "none" | "charm_99" | "charm_95"
}

PricingRuleInput is the request body for creating or updating a pricing rule.

type PricingRuleModifier

type PricingRuleModifier struct {
	Type       string         `json:"type"` // "geo", "loyalty", "bulk_duration", "quantity"
	Priority   int            `json:"priority"`
	Conditions map[string]any `json:"conditions"`
	Action     ModifierAction `json:"action"`
}

PricingRuleModifier is a single modifier entry inside a PricingRule.

type PricingRuleResponse

type PricingRuleResponse struct {
	ID string `json:"id"`
	PricingRuleInput
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

PricingRuleResponse wraps PricingRuleInput with server-assigned fields.

type PricingStage

type PricingStage interface {
	Name() string
	Apply(ctx *PriceContext) error
}

PricingStage is a single step in the pricing pipeline.

type PromoCodeInput

type PromoCodeInput struct {
	Code         string           `json:"code"`
	DiscountSpec DiscountSpec     `json:"discount_spec"`
	Eligibility  PromoEligibility `json:"eligibility"`
	Usage        PromoUsage       `json:"usage"`
	Validity     PromoValidity    `json:"validity"`
	Stacking     PromoStacking    `json:"stacking"`
	CampaignID   string           `json:"campaign_id"`
	Source       string           `json:"source"`
	CreatedBy    string           `json:"created_by"`
	IsActive     bool             `json:"is_active"`
}

PromoCodeInput is the request body for creating or updating a promo code.

type PromoCodeResponse

type PromoCodeResponse struct {
	ID string `json:"id"`
	PromoCodeInput
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

PromoCodeResponse wraps PromoCodeInput with server-assigned fields.

type PromoCodeUsedPayload

type PromoCodeUsedPayload struct {
	PromoCodeID   string `json:"promo_code_id"`
	Code          string `json:"code"`
	UserID        string `json:"user_id"`
	TariffID      string `json:"tariff_id"`
	DiscountCents int64  `json:"discount_cents"`
}

PromoCodeUsedPayload is emitted when a promo code is successfully used.

type PromoEligibility

type PromoEligibility struct {
	TariffIDs         []string `json:"tariff_ids,omitempty"`
	TariffIDsExclude  []string `json:"tariff_ids_exclude,omitempty"`
	AudienceSegments  []string `json:"audience_segments,omitempty"`
	Countries         []string `json:"countries,omitempty"`
	CountriesExclude  []string `json:"countries_exclude,omitempty"`
	FirstPurchaseOnly bool     `json:"first_purchase_only"`
	MinPurchaseCents  int64    `json:"min_purchase_cents"`
	MinDurationDays   int      `json:"min_duration_days"`
	RequiresTier      string   `json:"requires_tier"`
	UserIDsWhitelist  []string `json:"user_ids_whitelist,omitempty"`
	UserIDsBlacklist  []string `json:"user_ids_blacklist,omitempty"`
	PromoGroupIDs     []string `json:"promo_group_ids,omitempty"`
	PromoGroupExclude []string `json:"promo_group_exclude,omitempty"`
}

PromoEligibility defines who can use a promo code.

type PromoStacking

type PromoStacking struct {
	StackableWithOthers       bool `json:"stackable_with_others"`
	StackableWithPricingRules bool `json:"stackable_with_pricing_rules"`
	Priority                  int  `json:"priority"`
}

PromoStacking controls how promo codes interact with other discounts.

type PromoUsage

type PromoUsage struct {
	MaxUsesTotal     int `json:"max_uses_total"`
	MaxUsesPerUser   int `json:"max_uses_per_user"`
	MaxUsesPerTenant int `json:"max_uses_per_tenant"`
	UsesCount        int `json:"uses_count"`
	UniqueUsersCount int `json:"unique_users_count"`
}

PromoUsage tracks how many times a promo code has been used.

type PromoValidity

type PromoValidity struct {
	ValidFrom         string `json:"valid_from,omitempty"`
	ValidUntil        string `json:"valid_until,omitempty"`
	ValidOnDaysOfWeek []int  `json:"valid_on_days_of_week,omitempty"`
}

PromoValidity defines when a promo code is valid.

type StackingPolicy

type StackingPolicy string

StackingPolicy determines how multiple discounts combine.

const (
	StackingAdditive       StackingPolicy = "additive"
	StackingMultiplicative StackingPolicy = "multiplicative"
	StackingBestOnly       StackingPolicy = "best_only"
)

type TariffArchivedPayload

type TariffArchivedPayload struct {
	TariffID   string `json:"tariff_id"`
	ArchivedAt string `json:"archived_at"` // RFC3339
	Reason     string `json:"reason"`      // "admin_action" | "stock_depleted" | "sunset"
}

TariffArchivedPayload is emitted when a tariff is archived (soft-deleted).

type TariffInput

type TariffInput struct {
	// --- Core (original 13 fields) ---
	Name                 string   `json:"name"`
	Description          string   `json:"description"`
	PriceAmount          int64    `json:"price_amount"`
	PriceCurrency        string   `json:"price_currency"`
	DurationDays         int      `json:"duration_days"`
	TrafficLimitGB       float64  `json:"traffic_limit_gb"`
	DeviceLimit          int      `json:"device_limit"`
	MaxPurchasesPerUser  int      `json:"max_purchases_per_user"`
	VPNPanelID           string   `json:"vpn_panel_id,omitempty"`
	TrafficResetStrategy string   `json:"traffic_reset_strategy,omitempty"`
	InternalSquadUUIDs   []string `json:"internal_squad_uuids"`
	ExternalSquadUUIDs   []string `json:"external_squad_uuids"`
	Features             []string `json:"features"`
	IsActive             bool     `json:"is_active"`
	SortOrder            int      `json:"sort_order"`

	// --- Subscription periods ---
	PricingPeriods []PricingPeriod `json:"pricing_periods,omitempty"`

	// --- Billing model ---
	BillingModel     string `json:"billing_model"`
	AutoRenewal      bool   `json:"auto_renewal"`
	RenewalGraceDays int    `json:"renewal_grace_days"`

	// --- Pricing ---
	PricingRuleID       string           `json:"pricing_rule_id"`
	MultiCurrencyPrices map[string]int64 `json:"multi_currency_prices,omitempty"`
	PriceCharmStrategy  string           `json:"price_charm_strategy"`

	// --- PAYG / Hybrid ---
	IncludedTrafficGB float64 `json:"included_traffic_gb"`
	OveragePerGBCents int64   `json:"overage_per_gb_cents"`
	OverageRounding   string  `json:"overage_rounding"`

	// --- Credit model ---
	CreditCostPerDay int64 `json:"credit_cost_per_day"`
	CreditCostPerGB  int64 `json:"credit_cost_per_gb"`

	// --- Audience ---
	AudienceSegment string `json:"audience_segment"`
	MinSeats        int    `json:"min_seats"`
	MaxSeats        int    `json:"max_seats"`
	RequiresKYC     bool   `json:"requires_kyc"`

	// --- Trial & conversion ---
	TrialDays         int    `json:"trial_days"`
	TrialRequiresCard bool   `json:"trial_requires_card"`
	TrialToPlanID     string `json:"trial_to_plan_id"`

	// --- Lifecycle rules ---
	UpgradePolicy      string `json:"upgrade_policy"`
	DowngradePolicy    string `json:"downgrade_policy"`
	CancellationPolicy string `json:"cancellation_policy"`
	RefundWindowDays   int    `json:"refund_window_days"`

	// --- Purchase constraints ---
	AvailableFrom      string           `json:"available_from,omitempty"`
	AvailableUntil     string           `json:"available_until,omitempty"`
	StockLimit         int              `json:"stock_limit"`
	StockRemaining     int              `json:"stock_remaining"`
	RequiresInviteCode bool             `json:"requires_invite_code"`
	EligibilityRules   EligibilityRules `json:"eligibility_rules"`

	// --- Visibility ---
	VisibleInPublic         bool `json:"visible_in_public"`
	VisibleInTelegram       bool `json:"visible_in_telegram"`
	VisibleInCabinet        bool `json:"visible_in_cabinet"`
	VisibleInResellerPortal bool `json:"visible_in_reseller_portal"`

	// --- Reseller ---
	ResellerMarkupPct   int64 `json:"reseller_markup_pct"`
	ResellerMinPrice    int64 `json:"reseller_min_price"`
	ResellerMaxDiscount int64 `json:"reseller_max_discount"`

	// --- Localization ---
	LocalizedNames        map[string]string `json:"localized_names,omitempty"`
	LocalizedDescriptions map[string]string `json:"localized_descriptions,omitempty"`

	// --- Tax ---
	TaxCategory  string `json:"tax_category"`
	TaxInclusive bool   `json:"tax_inclusive"`

	// --- Metadata ---
	Tags         []string          `json:"tags,omitempty"`
	BadgeText    string            `json:"badge_text"`
	BadgeColor   string            `json:"badge_color"`
	ExternalRefs map[string]string `json:"external_refs,omitempty"`

	// --- Tenancy (RBAC Phase C6) ---
	// IsTemplate marks a platform-managed shared tariff that is readable by
	// every shop (read-filter in ListTariffs). Server-controlled: a shop actor
	// can never set this true (CreateTariff forces it false). Writes to a
	// stored template are platform-admin-only.
	IsTemplate bool `json:"is_template"`
}

TariffInput is the full set of fields accepted when creating or updating a tariff. The first 13 fields match the original schema; the remaining fields extend it with billing, pricing, audience, trial, lifecycle, visibility, reseller, localisation, tax, and metadata capabilities.

type TariffPublishedPayload

type TariffPublishedPayload struct {
	TariffID      string            `json:"tariff_id"`
	BillingModel  string            `json:"billing_model"`
	PriceAmount   int64             `json:"price_amount"`
	Currency      string            `json:"currency"`
	DurationDays  int               `json:"duration_days"`
	Version       int               `json:"version"`
	PricingRuleID string            `json:"pricing_rule_id,omitempty"`
	Metadata      map[string]string `json:"metadata,omitempty"`
}

TariffPublishedPayload is emitted when a tariff is created or updated, to sync with the billing domain's Plan aggregate.

type TariffPurchasedPayload

type TariffPurchasedPayload struct {
	TariffID       string           `json:"tariff_id"`
	UserID         string           `json:"user_id"`
	PriceBreakdown map[string]int64 `json:"price_breakdown"`
	AppliedRules   []string         `json:"applied_rules,omitempty"`
	ABExperiment   string           `json:"ab_experiment,omitempty"`
	ABVariant      string           `json:"ab_variant,omitempty"`
	Cohort         string           `json:"cohort,omitempty"`
	PromoCode      string           `json:"promo_code,omitempty"`
}

TariffPurchasedPayload is emitted asynchronously when a tariff is purchased.

type TariffReaderAdapter

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

TariffReaderAdapter adapts the tariff Handler to the bothost.TariffReader interface. It maps TariffResponse → bothost.TariffOffer, building per-period PlanIDs via DerivePlanID so that the planIDs presented to bot plugins match exactly the planIDs the checkout path resolves.

Tenant scoping is the caller's responsibility: the ctx passed to each method must carry the tenant GUC (wrapped in RunInTx(WithTenantID(...))); this adapter does not set the GUC itself.

func NewTariffReaderAdapter

func NewTariffReaderAdapter(lister visibleTariffLister, logger *slog.Logger) *TariffReaderAdapter

NewTariffReaderAdapter returns an adapter backed by lister. In production, pass the *tariff.Handler directly.

func (*TariffReaderAdapter) Get

Get implements bothost.TariffReader.

func (*TariffReaderAdapter) ListVisible

func (a *TariffReaderAdapter) ListVisible(ctx context.Context, channel string) ([]bothost.TariffOffer, error)

ListVisible implements bothost.TariffReader.

type TariffResponse

type TariffResponse struct {
	ID string `json:"id"`
	TariffInput
	CreatedAt string `json:"created_at"`
	UpdatedAt string `json:"updated_at"`
}

TariffResponse wraps TariffInput with server-assigned identity and timestamps.

Jump to

Keyboard shortcuts

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