package domain import ( "errors" "fmt" "strings" "unicode/utf16" "unicode/utf8" ) // PremiumPromoCatalog is the immutable, domain-only media catalog returned by // help.getPremiumPromo. VideoSections[i] describes Videos[i]; callers must // preserve the one-to-one ordering because official clients use positional // lookup. type PremiumPromoCatalog struct { VideoSections []string Videos []Document } const ( PremiumCurrencyStars = "XTR" PremiumDefaultFiatCurrency = "USD" PremiumPaymentFormTTLSeconds = 10 * 60 MaxPremiumGiftMessageRunes = 128 MaxPremiumGiftMessageEntities = 100 MaxPremiumPlanMonths = 120 MaxPremiumPlanDurationDays = 36500 MaxPremiumPlanAmountStars = int64(1_000_000_000_000_000) MaxPremiumUnixTimestamp = 1<<31 - 1 ) type PremiumPurchaseKind string const ( PremiumPurchaseSelf PremiumPurchaseKind = "self" PremiumPurchaseGift PremiumPurchaseKind = "gift" ) type PremiumEntitlementSource string const ( PremiumEntitlementPurchase PremiumEntitlementSource = "purchase" PremiumEntitlementGift PremiumEntitlementSource = "gift" PremiumEntitlementGiftCode PremiumEntitlementSource = "gift_code" PremiumEntitlementAdmin PremiumEntitlementSource = "admin" PremiumEntitlementPromo PremiumEntitlementSource = "promo" ) type PremiumEntitlementStatus string const ( PremiumEntitlementActive PremiumEntitlementStatus = "active" PremiumEntitlementExpired PremiumEntitlementStatus = "expired" PremiumEntitlementRevoked PremiumEntitlementStatus = "revoked" PremiumEntitlementRefunded PremiumEntitlementStatus = "refunded" ) type PremiumPaymentStatus string const ( PremiumPaymentPending PremiumPaymentStatus = "pending" PremiumPaymentProcessing PremiumPaymentStatus = "processing" PremiumPaymentPaid PremiumPaymentStatus = "paid" PremiumPaymentFailed PremiumPaymentStatus = "failed" PremiumPaymentRefunded PremiumPaymentStatus = "refunded" PremiumPaymentExpired PremiumPaymentStatus = "expired" ) // PremiumPlan is the authoritative server-side price and duration snapshot. // Version is copied into every payment intent, so a catalog edit can never // change the amount or duration of an already issued form. type PremiumPlan struct { Months int DurationDays int AmountStars int64 FiatCurrency string FiatAmount int64 StoreProduct string StoreQuantity int Enabled bool SortOrder int Label string ManagedBy string Version int64 UpdatedAt int } func (p PremiumPlan) Valid() bool { currency := p.EffectiveFiatCurrency() fiatAmount := p.EffectiveFiatAmount() return p.Months > 0 && p.Months <= MaxPremiumPlanMonths && p.DurationDays > 0 && p.DurationDays <= MaxPremiumPlanDurationDays && p.AmountStars > 0 && p.AmountStars <= MaxPremiumPlanAmountStars && p.Version > 0 && p.Label != "" && validPremiumCurrency(currency) && fiatAmount > 0 && fiatAmount <= MaxPremiumPlanAmountStars && len(p.StoreProduct) <= 256 && p.StoreQuantity >= 0 && ((p.StoreProduct == "" && p.StoreQuantity == 0) || (p.StoreProduct != "" && p.StoreQuantity > 0)) } func (p PremiumPlan) EffectiveFiatCurrency() string { currency := strings.ToUpper(strings.TrimSpace(p.FiatCurrency)) if currency == "" { return PremiumDefaultFiatCurrency } return currency } func (p PremiumPlan) EffectiveFiatAmount() int64 { if p.FiatAmount <= 0 { return p.AmountStars } return p.FiatAmount } func validPremiumCurrency(currency string) bool { if len(currency) != 3 { return false } for _, r := range currency { if r < 'A' || r > 'Z' { return false } } return true } const ( PremiumPlanManagedByConfig = "config" PremiumPlanManagedByAdmin = "admin" ) // PremiumPlanUpsertRequest is the operator-facing optimistic write model. // ExpectedVersion=0 creates a new month option; a positive value updates // exactly that revision. Plans are disabled instead of deleted because issued // payment forms retain their catalog snapshot and version. type PremiumPlanUpsertRequest struct { Months int DurationDays int AmountStars int64 FiatCurrency string FiatAmount int64 StoreProduct string StoreQuantity int Enabled bool SortOrder int Label string ExpectedVersion int64 ActorUserID int64 Date int Reason string CommandKey string } func (r PremiumPlanUpsertRequest) Valid() bool { return PremiumPlan{ Months: r.Months, DurationDays: r.DurationDays, AmountStars: r.AmountStars, FiatCurrency: r.FiatCurrency, FiatAmount: r.FiatAmount, StoreProduct: r.StoreProduct, StoreQuantity: r.StoreQuantity, Enabled: r.Enabled, SortOrder: r.SortOrder, Label: r.Label, Version: 1, }.Valid() && r.ExpectedVersion >= 0 && r.ActorUserID > 0 && r.Date > 0 && r.CommandKey != "" && len(r.CommandKey) <= 256 && len(r.Reason) <= 1024 } type PremiumGiftMessage struct { Text string `json:"text"` Entities []MessageEntity `json:"entities,omitempty"` } func (m PremiumGiftMessage) Valid() bool { if !utf8.ValidString(m.Text) || len([]rune(m.Text)) > MaxPremiumGiftMessageRunes || len(m.Entities) > MaxPremiumGiftMessageEntities { return false } encoded := utf16.Encode([]rune(m.Text)) units := len(encoded) boundaries := make([]bool, units+1) boundaries[0] = true position := 0 for _, r := range m.Text { position += len(utf16.Encode([]rune{r})) boundaries[position] = true } for _, entity := range m.Entities { if !PremiumGiftEntityTypeAllowed(entity.Type) { return false } if entity.Offset < 0 || entity.Length <= 0 || entity.Offset > units || entity.Length > units-entity.Offset || !boundaries[entity.Offset] || !boundaries[entity.Offset+entity.Length] { return false } if entity.Type == MessageEntityCustomEmoji && entity.DocumentID <= 0 { return false } } return true } // PremiumGiftEntityTypeAllowed mirrors the formatted-text subset accepted by // Telegram's giftPremiumSubscription surface. Keeping it in the domain model // makes MTProto, Bot API and persisted invoice validation identical. func PremiumGiftEntityTypeAllowed(entityType MessageEntityType) bool { switch entityType { case MessageEntityBold, MessageEntityItalic, MessageEntityUnderline, MessageEntityStrike, MessageEntitySpoiler, MessageEntityCustomEmoji: return true default: return false } } // PremiumInvoice is persisted as message media for invoices sent by // @premiumbot. It contains no client-controlled price. type PremiumInvoice struct { Kind PremiumPurchaseKind `json:"kind"` RecipientUserID int64 `json:"recipient_user_id,omitempty"` Months int `json:"months"` DurationDays int `json:"duration_days"` AmountStars int64 `json:"amount_stars"` PaymentCurrency string `json:"payment_currency,omitempty"` PaymentAmount int64 `json:"payment_amount,omitempty"` DebitStars bool `json:"debit_stars,omitempty"` PlanVersion int64 `json:"plan_version"` Title string `json:"title"` Description string `json:"description"` StartParam string `json:"start_param,omitempty"` Message PremiumGiftMessage `json:"message,omitempty"` } func (i PremiumInvoice) Valid() bool { if i.Kind != PremiumPurchaseSelf && i.Kind != PremiumPurchaseGift { return false } if i.Kind == PremiumPurchaseSelf && i.RecipientUserID != 0 { return false } if i.Kind == PremiumPurchaseGift && i.RecipientUserID <= 0 { return false } return i.Months > 0 && i.Months <= MaxPremiumPlanMonths && i.DurationDays > 0 && i.DurationDays <= MaxPremiumPlanDurationDays && i.AmountStars > 0 && i.AmountStars <= MaxPremiumPlanAmountStars && i.PlanVersion > 0 && i.Title != "" && i.Description != "" && validPremiumCurrency(i.EffectivePaymentCurrency()) && i.EffectivePaymentAmount() > 0 && i.EffectivePaymentAmount() <= MaxPremiumPlanAmountStars && i.Message.Valid() } func (i PremiumInvoice) EffectivePaymentCurrency() string { if currency := strings.ToUpper(strings.TrimSpace(i.PaymentCurrency)); currency != "" { return currency } return PremiumCurrencyStars } func (i PremiumInvoice) EffectivePaymentAmount() int64 { if i.PaymentAmount > 0 { return i.PaymentAmount } return i.AmountStars } func (i PremiumInvoice) EffectiveDebitStars() bool { return i.PaymentCurrency == "" || i.EffectivePaymentCurrency() == PremiumCurrencyStars || i.DebitStars } type PremiumPaymentForm struct { ID int64 IdempotencyKey string BuyerUserID int64 Kind PremiumPurchaseKind RecipientUserID int64 Months int DurationDays int AmountStars int64 PaymentCurrency string PaymentAmount int64 DebitStars bool PlanVersion int64 Message PremiumGiftMessage IssuedAt int ExpiresAt int } func (f PremiumPaymentForm) Valid() bool { if f.ID != 0 || f.BuyerUserID <= 0 || len(f.IdempotencyKey) > 256 || f.IssuedAt > MaxPremiumUnixTimestamp-PremiumPaymentFormTTLSeconds || f.IssuedAt <= 0 || f.ExpiresAt != f.IssuedAt+PremiumPaymentFormTTLSeconds || f.Months <= 0 || f.Months > MaxPremiumPlanMonths || f.DurationDays <= 0 || f.DurationDays > MaxPremiumPlanDurationDays || f.AmountStars <= 0 || f.AmountStars > MaxPremiumPlanAmountStars || f.PlanVersion <= 0 || !validPremiumCurrency(f.EffectivePaymentCurrency()) || f.EffectivePaymentAmount() <= 0 || f.EffectivePaymentAmount() > MaxPremiumPlanAmountStars || !f.Message.Valid() { return false } switch f.Kind { case PremiumPurchaseSelf: return f.RecipientUserID == f.BuyerUserID case PremiumPurchaseGift: return f.RecipientUserID > 0 && f.RecipientUserID != f.BuyerUserID default: return false } } func (f PremiumPaymentForm) EffectivePaymentCurrency() string { if currency := strings.ToUpper(strings.TrimSpace(f.PaymentCurrency)); currency != "" { return currency } return PremiumCurrencyStars } func (f PremiumPaymentForm) EffectivePaymentAmount() int64 { if f.PaymentAmount > 0 { return f.PaymentAmount } return f.AmountStars } func (f PremiumPaymentForm) EffectiveDebitStars() bool { return f.PaymentCurrency == "" || f.EffectivePaymentCurrency() == PremiumCurrencyStars || f.DebitStars } type PremiumPurchaseRequest struct { BuyerUserID int64 FormID int64 Kind PremiumPurchaseKind RecipientUserID int64 Months int PlanVersion int64 Message PremiumGiftMessage Date int CommandKey string OriginAuthKeyID [8]byte OriginSessionID int64 RecipientBlocked bool } type PremiumEntitlement struct { ID int64 `json:"id"` UserID int64 `json:"user_id"` Source PremiumEntitlementSource `json:"source"` SourceUserID int64 `json:"source_user_id"` PaymentIntentID int64 `json:"payment_intent_id,omitempty"` TransactionID int64 `json:"transaction_id,omitempty"` Months int `json:"months"` DurationDays int `json:"duration_days"` StartsAt int `json:"starts_at"` ExpiresAt int `json:"expires_at"` Status PremiumEntitlementStatus `json:"status"` CommandKey string `json:"command_key,omitempty"` CreatedAt int `json:"created_at"` } // PremiumPaymentIntent is the immutable checkout snapshot plus lifecycle // pointers used by the protected operator read surface. type PremiumPaymentIntent struct { ID int64 `json:"id"` FormID int64 `json:"form_id"` IdempotencyKey string `json:"idempotency_key"` BuyerUserID int64 `json:"buyer_user_id"` Kind PremiumPurchaseKind `json:"kind"` RecipientUserID int64 `json:"recipient_user_id"` Months int `json:"months"` DurationDays int `json:"duration_days"` AmountStars int64 `json:"amount_stars"` Currency string `json:"currency"` PaymentAmount int64 `json:"payment_amount"` DebitStars bool `json:"debit_stars"` PlanVersion int64 `json:"plan_version"` Message PremiumGiftMessage `json:"message"` Status PremiumPaymentStatus `json:"status"` IssuedAt int `json:"issued_at"` ExpiresAt int `json:"expires_at"` PaidAt int `json:"paid_at,omitempty"` RefundedAt int `json:"refunded_at,omitempty"` StarsTransactionID int64 `json:"stars_transaction_id,omitempty"` SenderMessageID int `json:"sender_message_id,omitempty"` RecipientMessageID int `json:"recipient_message_id,omitempty"` CreatedAt int `json:"created_at"` UpdatedAt int `json:"updated_at"` } type PremiumPaymentDetails struct { Intent PremiumPaymentIntent `json:"payment_intent"` Entitlement PremiumEntitlement `json:"entitlement"` Transaction StarsTransaction `json:"ledger_transaction"` } type PremiumPurchaseResult struct { Form PremiumPaymentForm Entitlement PremiumEntitlement User User Balance StarsBalance Send SendPrivateTextResult Duplicate bool } type PremiumAdminGrantRequest struct { UserID int64 ActorUserID int64 Months int DurationDays int Date int Reason string CommandKey string } type PremiumAdminRevokeRequest struct { UserID int64 ActorUserID int64 EntitlementID int64 Date int Reason string CommandKey string } type PremiumRefundRequest struct { PaymentIntentID int64 ActorUserID int64 Date int Reason string CommandKey string } // PremiumSubscriptionActiveError prevents the single-recipient Premium gift // flow from silently stacking another entitlement on an already active one. // Until is the recipient's current expiry as a Unix timestamp. type PremiumSubscriptionActiveError struct { Until int } func (e PremiumSubscriptionActiveError) Error() string { return fmt.Sprintf("premium: subscription active until %d", e.Until) } var ( ErrPremiumPlanInvalid = errors.New("premium: invalid plan") ErrPremiumPlanUnavailable = errors.New("premium: plan unavailable") ErrPremiumPlanConflict = errors.New("premium: plan version conflict") ErrPremiumLastPlan = errors.New("premium: at least one enabled plan is required") ErrPremiumRecipientInvalid = errors.New("premium: invalid recipient") ErrPremiumRecipientRestricted = errors.New("premium: recipient disallows premium gifts") ErrPremiumGiftSelf = errors.New("premium: gifting self is not allowed") ErrPremiumGiftMessageInvalid = errors.New("premium: invalid gift message") ErrPremiumFormExpired = errors.New("premium: payment form expired") ErrPremiumFormInvalid = errors.New("premium: invalid payment form") ErrPremiumFormAmountChanged = errors.New("premium: payment form amount changed") ErrPremiumInvoiceAlreadyPaid = errors.New("premium: invoice already paid") ErrPremiumPaymentNotFound = errors.New("premium: payment not found") ErrPremiumAlreadyRefunded = errors.New("premium: payment already refunded") ErrPremiumExternalRefund = errors.New("premium: external payment must be refunded by its provider") ) func PremiumPurchaseDescription(kind PremiumPurchaseKind, months int, recipientID int64) string { if kind == PremiumPurchaseGift { return fmt.Sprintf("Premium gift: %d month(s) for user %d", months, recipientID) } return fmt.Sprintf("Premium subscription: %d month(s)", months) }