package models import ( "time" "github.com/google/uuid" ) // DiscountType is the kind of discount a code grants. Exactly one value field // on the code is populated per type (see the DB CHECK in 000048). type DiscountType string const ( DiscountTypePercent DiscountType = "percent" DiscountTypeFixed DiscountType = "fixed" DiscountTypeTrialExtension DiscountType = "trial_extension" ) // DiscountDuration mirrors Stripe coupon duration for money discounts. type DiscountDuration string const ( DiscountDurationOnce DiscountDuration = "once" DiscountDurationRepeating DiscountDuration = "repeating" DiscountDurationForever DiscountDuration = "forever" ) type DiscountCodeStatus string const ( DiscountCodeStatusActive DiscountCodeStatus = "active" DiscountCodeStatusDisabled DiscountCodeStatus = "disabled" DiscountCodeStatusExpired DiscountCodeStatus = "expired" ) type DiscountRedemptionStatus string const ( DiscountRedemptionStatusPending DiscountRedemptionStatus = "pending" DiscountRedemptionStatusApplied DiscountRedemptionStatus = "applied" DiscountRedemptionStatusCanceled DiscountRedemptionStatus = "canceled" ) // IsMoney reports whether the discount reduces the charged amount (vs granting // extra trial days). func (t DiscountType) IsMoney() bool { return t == DiscountTypePercent || t == DiscountTypeFixed } // DiscountCode is an admin-managed promo code. type DiscountCode struct { ID uuid.UUID `json:"id"` Code string `json:"code"` Description string `json:"description"` Type DiscountType `json:"type"` PercentOff *int `json:"percent_off,omitempty"` AmountOff *float64 `json:"amount_off,omitempty"` Currency *string `json:"currency,omitempty"` TrialExtensionDays *int `json:"trial_extension_days,omitempty"` Duration DiscountDuration `json:"duration"` DurationInMonths *int `json:"duration_in_months,omitempty"` MaxRedemptions *int `json:"max_redemptions,omitempty"` TimesRedeemed int `json:"times_redeemed"` PerAccountLimit int `json:"per_account_limit"` AppliesToAllPlans bool `json:"applies_to_all_plans"` PlanIDs []uuid.UUID `json:"plan_ids"` Status DiscountCodeStatus `json:"status"` StartsAt *time.Time `json:"starts_at,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"` CreatedBy *uuid.UUID `json:"created_by,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // DiscountRedemption records a single redemption of a code by an organization. type DiscountRedemption struct { ID uuid.UUID `json:"id"` DiscountCodeID uuid.UUID `json:"discount_code_id"` OrganizationID uuid.UUID `json:"organization_id"` RedeemedBy *uuid.UUID `json:"redeemed_by,omitempty"` SubscriptionID *uuid.UUID `json:"subscription_id,omitempty"` PlanID *uuid.UUID `json:"plan_id,omitempty"` StripeCouponID *string `json:"stripe_coupon_id,omitempty"` StripeCheckoutSessionID *string `json:"stripe_checkout_session_id,omitempty"` Type DiscountType `json:"type"` PercentOff *int `json:"percent_off,omitempty"` AmountOff *float64 `json:"amount_off,omitempty"` Currency *string `json:"currency,omitempty"` TrialExtensionDays *int `json:"trial_extension_days,omitempty"` Status DiscountRedemptionStatus `json:"status"` RedeemedAt time.Time `json:"redeemed_at"` AppliedAt *time.Time `json:"applied_at,omitempty"` // Joined (populated by admin list-by-code). Code string `json:"code,omitempty"` } // CreateDiscountCodeRequest is the admin create payload. type CreateDiscountCodeRequest struct { Code string `json:"code" binding:"required"` Description string `json:"description"` Type DiscountType `json:"type" binding:"required"` PercentOff *int `json:"percent_off,omitempty"` AmountOff *float64 `json:"amount_off,omitempty"` Currency *string `json:"currency,omitempty"` TrialExtensionDays *int `json:"trial_extension_days,omitempty"` Duration DiscountDuration `json:"duration,omitempty"` DurationInMonths *int `json:"duration_in_months,omitempty"` MaxRedemptions *int `json:"max_redemptions,omitempty"` PerAccountLimit *int `json:"per_account_limit,omitempty"` AppliesToAllPlans bool `json:"applies_to_all_plans"` PlanIDs []uuid.UUID `json:"plan_ids,omitempty"` Status DiscountCodeStatus `json:"status,omitempty"` StartsAt *time.Time `json:"starts_at,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"` } // UpdateDiscountCodeRequest is the admin partial-update payload. The discount // `type` is immutable; recreate the code to change kinds. type UpdateDiscountCodeRequest struct { Description *string `json:"description,omitempty"` PercentOff *int `json:"percent_off,omitempty"` AmountOff *float64 `json:"amount_off,omitempty"` Currency *string `json:"currency,omitempty"` TrialExtensionDays *int `json:"trial_extension_days,omitempty"` Duration *DiscountDuration `json:"duration,omitempty"` DurationInMonths *int `json:"duration_in_months,omitempty"` MaxRedemptions *int `json:"max_redemptions,omitempty"` PerAccountLimit *int `json:"per_account_limit,omitempty"` AppliesToAllPlans *bool `json:"applies_to_all_plans,omitempty"` PlanIDs *[]uuid.UUID `json:"plan_ids,omitempty"` Status *DiscountCodeStatus `json:"status,omitempty"` StartsAt *time.Time `json:"starts_at,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"` } // AdminDiscountSearch filters the admin discount list. Note the free-text key // is "search" (not "q") to preserve the existing endpoint contract. type AdminDiscountSearch struct { Search string `form:"search"` Status string `form:"status"` // active|disabled|expired|all Type string `form:"type"` // percent|fixed|trial_extension Duration string `form:"duration"` // once|repeating|forever // Plan eligibility PlanScope string `form:"plan_scope"` // all|specific PlanID *uuid.UUID `form:"plan_id"` // Flag / relationship existence HasRedemptions bool `form:"has_redemptions"` HasMaxRedemptions bool `form:"has_max_redemptions"` Exhausted bool `form:"exhausted"` HasExpiry bool `form:"has_expiry"` // Count ranges TimesRedeemedMin *int `form:"times_redeemed_min"` TimesRedeemedMax *int `form:"times_redeemed_max"` PercentOffMin *int `form:"percent_off_min"` PercentOffMax *int `form:"percent_off_max"` // Date ranges (YYYY-MM-DD, UTC) CreatedWithin int `form:"created_within"` // days; 0 = any CreatedAfter *time.Time `form:"created_after" time_format:"2006-01-02" time_utc:"true"` CreatedBefore *time.Time `form:"created_before" time_format:"2006-01-02" time_utc:"true"` StartsAfter *time.Time `form:"starts_after" time_format:"2006-01-02" time_utc:"true"` StartsBefore *time.Time `form:"starts_before" time_format:"2006-01-02" time_utc:"true"` ExpiresAfter *time.Time `form:"expires_after" time_format:"2006-01-02" time_utc:"true"` ExpiresBefore *time.Time `form:"expires_before" time_format:"2006-01-02" time_utc:"true"` Cursor *uuid.UUID `form:"cursor"` Limit int `form:"limit"` SortBy string `form:"sort_by"` // created_at|code|status|times_redeemed|expires_at|starts_at|updated_at SortDesc bool `form:"sort_desc"` } // AdminDiscountsResult is the paginated admin discount list envelope. type AdminDiscountsResult struct { Data []DiscountCode `json:"data"` Pagination Pagination `json:"pagination"` } // AdminDiscountRedemptionsResult is the paginated redemptions envelope. type AdminDiscountRedemptionsResult struct { Data []DiscountRedemption `json:"data"` Pagination Pagination `json:"pagination"` } // ValidateDiscountRequest is the customer pre-checkout validation payload. type ValidateDiscountRequest struct { Code string `json:"code" binding:"required"` PlanID *uuid.UUID `json:"plan_id,omitempty"` } // DiscountPreview is the customer-facing validation result. When invalid, // Reason explains why; the discount fields are only populated when Valid. type DiscountPreview struct { Valid bool `json:"valid"` Reason string `json:"reason,omitempty"` Code string `json:"code,omitempty"` Type DiscountType `json:"type,omitempty"` PercentOff *int `json:"percent_off,omitempty"` AmountOff *float64 `json:"amount_off,omitempty"` Currency *string `json:"currency,omitempty"` TrialExtensionDays *int `json:"trial_extension_days,omitempty"` Duration DiscountDuration `json:"duration,omitempty"` DurationInMonths *int `json:"duration_in_months,omitempty"` // Plan-specific computation, only when a plan_id is supplied and the code is // a money discount. OriginalAmount *float64 `json:"original_amount,omitempty"` DiscountedAmount *float64 `json:"discounted_amount,omitempty"` SavingsAmount *float64 `json:"savings_amount,omitempty"` }