feat(payments): wallet screen with balances, benefits and storefront
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
Add the "Кошелёк" section to the settings hub: context-visible chip balances, active benefits (no-ads term/forever, hints) and a storefront of chip-priced values and money-priced chip packs. Guests have no wallet; the Google Play build hides the money purchases behind a RuStore stub; a web purchase that would draw VK/Telegram chips warns first. Add the catalog read path the storefront needs — a context-projected GET /api/v1/user/wallet/catalog (payments service + store, gateway op wallet.catalog, FBS Catalog/CatalogProduct/CatalogAtom, client decode) — plus the client leg for the existing wallet.get/buy ops. Value spends reuse the existing spend path; the chip-pack purchase (money order flow) arrives with payment intake, so its action is a disabled placeholder for now. Covered by Go unit (catalog projection) + integration (/wallet/catalog over Postgres), vitest (formatting, spendable selection, web-spend warning, GP flag, codec + gateway encode round-trips) and Playwright mock e2e (render, guest-hidden, GP stub, warning) on Chromium + WebKit.
This commit is contained in:
@@ -67,8 +67,10 @@ func (s *Server) registerRoutes() {
|
||||
u.GET("/block-status", s.handleBlockStatus)
|
||||
}
|
||||
if s.payments != nil {
|
||||
// The wallet: the context-visible chip segments + benefits, and a chip spend on a value.
|
||||
// The wallet: the context-visible chip segments + benefits, the storefront catalog, and a
|
||||
// chip spend on a value.
|
||||
u.GET("/wallet", s.handleWallet)
|
||||
u.GET("/wallet/catalog", s.handleWalletCatalog)
|
||||
u.POST("/wallet/buy", s.handleWalletBuy)
|
||||
}
|
||||
if s.links != nil {
|
||||
|
||||
@@ -45,6 +45,68 @@ func walletDTOFrom(v payments.WalletView) walletDTO {
|
||||
return out
|
||||
}
|
||||
|
||||
// catalogAtomDTO is one atom line of a storefront product: the base value type it grants and how
|
||||
// many of it the product carries.
|
||||
type catalogAtomDTO struct {
|
||||
AtomType string `json:"atom_type"`
|
||||
Quantity int `json:"quantity"`
|
||||
}
|
||||
|
||||
// catalogProductDTO is one storefront product for the caller's context: a chip-priced value
|
||||
// (chips set, no money price) or a chip pack priced in the context's payment method
|
||||
// (money_amount minor units + money_currency, no chips), plus what it grants.
|
||||
type catalogProductDTO struct {
|
||||
Kind string `json:"kind"`
|
||||
ProductID string `json:"product_id"`
|
||||
Title string `json:"title"`
|
||||
Chips int `json:"chips"`
|
||||
MoneyAmount int64 `json:"money_amount"`
|
||||
MoneyCurrency string `json:"money_currency"`
|
||||
Atoms []catalogAtomDTO `json:"atoms"`
|
||||
}
|
||||
|
||||
// catalogDTO is the storefront: the products visible and purchasable in the caller's context.
|
||||
type catalogDTO struct {
|
||||
Products []catalogProductDTO `json:"products"`
|
||||
}
|
||||
|
||||
// catalogDTOFrom projects a payments catalog view into the wire DTO.
|
||||
func catalogDTOFrom(v payments.CatalogView) catalogDTO {
|
||||
out := catalogDTO{Products: make([]catalogProductDTO, 0, len(v.Products))}
|
||||
for _, p := range v.Products {
|
||||
atoms := make([]catalogAtomDTO, 0, len(p.Atoms))
|
||||
for _, a := range p.Atoms {
|
||||
atoms = append(atoms, catalogAtomDTO{AtomType: a.AtomType, Quantity: a.Quantity})
|
||||
}
|
||||
out.Products = append(out.Products, catalogProductDTO{
|
||||
Kind: p.Kind, ProductID: p.ProductID, Title: p.Title,
|
||||
Chips: p.Chips, MoneyAmount: p.MoneyAmount, MoneyCurrency: p.MoneyCurrency, Atoms: atoms,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// handleWalletCatalog returns the storefront for the caller's trusted execution context: the
|
||||
// chip-priced values and the chip packs priced in the context's payment method.
|
||||
func (s *Server) handleWalletCatalog(c *gin.Context) {
|
||||
uid, ok := userID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ctx := c.Request.Context()
|
||||
cxt, _, err := s.walletGate(ctx, uid)
|
||||
if err != nil {
|
||||
s.abortErr(c, err)
|
||||
return
|
||||
}
|
||||
view, err := s.payments.Catalog(ctx, cxt)
|
||||
if err != nil {
|
||||
s.abortErr(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, catalogDTOFrom(view))
|
||||
}
|
||||
|
||||
// handleWallet returns the caller's wallet — the segments and benefits visible in the current
|
||||
// trusted execution context.
|
||||
func (s *Server) handleWallet(c *gin.Context) {
|
||||
|
||||
Reference in New Issue
Block a user