internal/domain/item_prices.go
Rama main · réplica de lectura del repositorio original.
package domain
import "sort"
// ItemPrice is an admin-configurable price for a catalog product. A row in
// item_prices overrides the catalog default for that product; enabled=false
// hides the product from the shop. Products without a row follow
// DefaultItemPrices.
type ItemPrice struct {
ProductCode string `json:"product_code"` // e.g., "premium_1m", "uname_10"
Title string `json:"title,omitempty"`
StarsPrice int `json:"stars_price"` // price in Telegram Stars
Bid int64 `json:"bid"` // for username products (TON bid)
Enabled bool `json:"enabled"`
UpdatedBy string `json:"updated_by,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"` // unix timestamp
}
// ItemPriceListRequest filters the price list.
type ItemPriceListRequest struct {
EnabledOnly bool `json:"enabled_only"`
}
// ItemPriceUpdateRequest updates a single product's price.
type ItemPriceUpdateRequest struct {
ProductCode string `json:"product_code"`
StarsPrice int `json:"stars_price"`
Bid int64 `json:"bid"`
Enabled bool `json:"enabled"`
}
// ShopSettingStarsRate is the shop_settings key holding how many server Stars
// one Telegram Star buys — the rate the bot multiplies a Stars purchase by, and
// the only input to the stars_N packages.
const ShopSettingStarsRate = "stars_rate"
// DefaultItemPrices mirrors the `fixed` array of the store bot's catalog.js
// (cmd/bots/grammystore/src/catalog.js): the prices a fresh deployment shows
// before any override row exists. The bot treats the server list as
// authoritative for what it charges, so an admin edit here changes the shop.
//
// This list is a copy of another repository's file, and a copy is what goes
// stale quietly. It had: three products were priced differently from the bot,
// two of the three username tiers were missing entirely -- so they could never
// be priced from the panel at all -- and three rows were listed for products
// the bot does not sell. TestDefaultItemPricesMirrorBotCatalog parses
// catalog.js and fails on any difference, in either direction; keep the two in
// step by changing both, not this file alone.
//
// Titles come from the bot's catalog too, minus the FlashGram prefix: the
// server's own name is configurable (see Server Settings -> Server identity)
// and the shop should read as the operator's, not as the reference build's.
//
// The bot's stars_N packages are deliberately absent. It derives them from
// shop_settings.stars_rate rather than reading a per-product price, so there is
// nothing for a row here to override -- they are surfaced on the admin page as
// read-only instead. TestBotStarPackagesAreNotPriced pins that.
var DefaultItemPrices = []ItemPrice{
{ProductCode: "premium_1m", Title: "Premium — 1 month", StarsPrice: 20, Enabled: true},
{ProductCode: "premium_3m", Title: "Premium — 3 months", StarsPrice: 40, Enabled: true},
{ProductCode: "num_short", Title: "Anonymous +888 8 XXX", StarsPrice: 50, Enabled: true},
{ProductCode: "num_long", Title: "Anonymous +888 0XXX XXXX", StarsPrice: 25, Enabled: true},
{ProductCode: "uname_10", Title: "Collectible username — 10 TON", StarsPrice: 10, Bid: 10, Enabled: true},
{ProductCode: "uname_100", Title: "Collectible username — 100 TON", StarsPrice: 30, Bid: 100, Enabled: true},
{ProductCode: "uname_1000", Title: "Collectible username — 1000 TON", StarsPrice: 30, Bid: 1000, Enabled: true},
}
// DefaultStarsPackageSizes are the Telegram-Star package sizes the store bot
// offers, in the order catalog.js lists them. Each is sold for that many
// Telegram Stars and credits package * stars_rate server Stars, so its price is
// the package size itself rather than a per-product figure: the admin page
// lists these read-only, and the single rate above them is what changes them.
var DefaultStarsPackageSizes = []int{1, 5, 10, 25, 50, 100}
// MergeItemPrices overlays stored overrides onto the catalog defaults. Rows
// for codes the defaults do not know (a product added on the bot side first)
// are appended after the defaults so a new product is still editable here
// without a backend release.
func MergeItemPrices(defaults, overrides []ItemPrice, enabledOnly bool) []ItemPrice {
byCode := make(map[string]ItemPrice, len(overrides))
for _, o := range overrides {
byCode[o.ProductCode] = o
}
out := make([]ItemPrice, 0, len(defaults)+len(overrides))
known := make(map[string]struct{}, len(defaults))
for _, d := range defaults {
p := d
if o, ok := byCode[d.ProductCode]; ok {
p.StarsPrice = o.StarsPrice
p.Bid = o.Bid
p.Enabled = o.Enabled
p.UpdatedBy = o.UpdatedBy
p.UpdatedAt = o.UpdatedAt
}
known[d.ProductCode] = struct{}{}
if enabledOnly && !p.Enabled {
continue
}
out = append(out, p)
}
var extra []ItemPrice
for _, o := range overrides {
if _, ok := known[o.ProductCode]; ok {
continue
}
if enabledOnly && !o.Enabled {
continue
}
extra = append(extra, o)
}
sort.Slice(extra, func(i, j int) bool { return extra[i].ProductCode < extra[j].ProductCode })
return append(out, extra...)
}