package payments import ( "context" "fmt" "github.com/go-jet/jet/v2/postgres" "github.com/google/uuid" "scrabble/backend/internal/postgres/jet/payments/model" "scrabble/backend/internal/postgres/jet/payments/table" ) // atomQty is one atom line of a product as loaded from the catalog: the atom type and quantity. type atomQty struct { atomType string quantity int } // priceRow is one price of a product as loaded from the catalog: the payment method (empty for a // value's CHIP price, stored with a NULL method) and the amount in that currency's minor units. type priceRow struct { method string currency Currency amount int64 } // catalogEntry is a raw active product loaded for the storefront: its atom composition and every // price row. [projectCatalog] turns it into the context-visible [CatalogProduct]. type catalogEntry struct { id uuid.UUID title string atoms []atomQty prices []priceRow } // loadCatalog reads every active product with its atoms and prices, ordered by creation, straight // from the catalog tables. The catalog is small and rarely edited (admin console only), so it is // read uncached — unlike the per-account balances/benefits the read cache fronts. func (s *Store) loadCatalog(ctx context.Context) ([]catalogEntry, error) { var prods []model.Product if err := postgres.SELECT(table.Product.AllColumns). FROM(table.Product). WHERE(table.Product.Active.IS_TRUE()). ORDER_BY(table.Product.CreatedAt.ASC()). QueryContext(ctx, s.db, &prods); err != nil { return nil, fmt.Errorf("payments: load catalog products: %w", err) } if len(prods) == 0 { return nil, nil } entries := make([]catalogEntry, len(prods)) index := make(map[uuid.UUID]int, len(prods)) for i, p := range prods { entries[i] = catalogEntry{id: p.ProductID, title: p.Title} index[p.ProductID] = i } var items []model.ProductItem if err := postgres.SELECT(table.ProductItem.AllColumns). FROM(table.ProductItem). QueryContext(ctx, s.db, &items); err != nil { return nil, fmt.Errorf("payments: load catalog items: %w", err) } for _, it := range items { if i, ok := index[it.ProductID]; ok { entries[i].atoms = append(entries[i].atoms, atomQty{atomType: it.AtomType, quantity: int(it.Quantity)}) } } var prices []model.ProductPrice if err := postgres.SELECT(table.ProductPrice.AllColumns). FROM(table.ProductPrice). QueryContext(ctx, s.db, &prices); err != nil { return nil, fmt.Errorf("payments: load catalog prices: %w", err) } for _, pr := range prices { i, ok := index[pr.ProductID] if !ok { continue // a price for a deactivated product — not in the storefront } method := "" if pr.Method != nil { method = *pr.Method } entries[i].prices = append(entries[i].prices, priceRow{method: method, currency: Currency(pr.Currency), amount: pr.Amount}) } return entries, nil }