fix(game): require coin balance for room rps
This commit is contained in:
parent
2a488daeed
commit
d9433647d3
@ -62,6 +62,11 @@ type GiftCatalogClient interface {
|
|||||||
ListGiftConfigs(ctx context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error)
|
ListGiftConfigs(ctx context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BalanceClient 只暴露 room-rps 入场前需要的钱包余额查询;PK 状态仍由 game-service 管,金币事实仍由 wallet-service 管。
|
||||||
|
type BalanceClient interface {
|
||||||
|
GetBalances(ctx context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ConfigStore 是 room-rps 配置的持久化边界;service 只关心完整配置快照,表结构和 JSON 编码留在 MySQL 仓储层。
|
// ConfigStore 是 room-rps 配置的持久化边界;service 只关心完整配置快照,表结构和 JSON 编码留在 MySQL 仓储层。
|
||||||
type ConfigStore interface {
|
type ConfigStore interface {
|
||||||
GetRoomRPSConfig(ctx context.Context, appCode string, gameID string) (*gamev1.RoomRPSConfig, error)
|
GetRoomRPSConfig(ctx context.Context, appCode string, gameID string) (*gamev1.RoomRPSConfig, error)
|
||||||
@ -83,6 +88,7 @@ type Service struct {
|
|||||||
config Config
|
config Config
|
||||||
user UserClient
|
user UserClient
|
||||||
gifts GiftCatalogClient
|
gifts GiftCatalogClient
|
||||||
|
wallet BalanceClient
|
||||||
pub Publisher
|
pub Publisher
|
||||||
store ConfigStore
|
store ConfigStore
|
||||||
|
|
||||||
@ -104,13 +110,18 @@ func New(config Config, user UserClient, publisher Publisher, extras ...any) *Se
|
|||||||
config.RevealCountdown = 3 * time.Second
|
config.RevealCountdown = 3 * time.Second
|
||||||
}
|
}
|
||||||
var gifts GiftCatalogClient
|
var gifts GiftCatalogClient
|
||||||
|
var wallet BalanceClient
|
||||||
var store ConfigStore
|
var store ConfigStore
|
||||||
for _, extra := range extras {
|
for _, extra := range extras {
|
||||||
switch typed := extra.(type) {
|
if typed, ok := extra.(GiftCatalogClient); ok {
|
||||||
case GiftCatalogClient:
|
|
||||||
// room-rps 运行配置只保存四个 gift_id;后台不再携带展示字段时,必须从 wallet-service 读取真实礼物快照。
|
// room-rps 运行配置只保存四个 gift_id;后台不再携带展示字段时,必须从 wallet-service 读取真实礼物快照。
|
||||||
gifts = typed
|
gifts = typed
|
||||||
case ConfigStore:
|
}
|
||||||
|
if typed, ok := extra.(BalanceClient); ok {
|
||||||
|
// 发起和应战都必须在进入状态机前查 COIN 余额;不做余额查询时会让 0 金币用户进入 PK 链路。
|
||||||
|
wallet = typed
|
||||||
|
}
|
||||||
|
if typed, ok := extra.(ConfigStore); ok {
|
||||||
// MySQL 是配置事实源;没有仓储的测试场景才落回进程内 map,避免把运行态差异写成前端兜底。
|
// MySQL 是配置事实源;没有仓储的测试场景才落回进程内 map,避免把运行态差异写成前端兜底。
|
||||||
store = typed
|
store = typed
|
||||||
}
|
}
|
||||||
@ -119,6 +130,7 @@ func New(config Config, user UserClient, publisher Publisher, extras ...any) *Se
|
|||||||
config: config,
|
config: config,
|
||||||
user: user,
|
user: user,
|
||||||
gifts: gifts,
|
gifts: gifts,
|
||||||
|
wallet: wallet,
|
||||||
pub: publisher,
|
pub: publisher,
|
||||||
store: store,
|
store: store,
|
||||||
configs: make(map[string]*gamev1.RoomRPSConfig),
|
configs: make(map[string]*gamev1.RoomRPSConfig),
|
||||||
@ -301,6 +313,14 @@ func (s *Service) CreateChallenge(ctx context.Context, req *gamev1.CreateRoomRPS
|
|||||||
if !ok || !gift.GetEnabled() {
|
if !ok || !gift.GetEnabled() {
|
||||||
return nil, 0, xerr.New(xerr.InvalidArgument, "room rps gift is not enabled")
|
return nil, 0, xerr.New(xerr.InvalidArgument, "room rps gift is not enabled")
|
||||||
}
|
}
|
||||||
|
stakeCoin := gift.GetGiftPriceCoin()
|
||||||
|
if stakeCoin <= 0 {
|
||||||
|
return nil, 0, xerr.New(xerr.InvalidArgument, "room rps stake coin is invalid")
|
||||||
|
}
|
||||||
|
initiatorBalance, err := s.ensureCoinBalance(ctx, req.GetMeta().GetRequestId(), app, req.GetUserId(), stakeCoin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
now := s.now()
|
now := s.now()
|
||||||
@ -313,8 +333,8 @@ func (s *Service) CreateChallenge(ctx context.Context, req *gamev1.CreateRoomRPS
|
|||||||
Status: StatusPending,
|
Status: StatusPending,
|
||||||
StakeGiftId: legacyRoomRPSGiftID(giftID),
|
StakeGiftId: legacyRoomRPSGiftID(giftID),
|
||||||
StakeGiftIdText: giftID,
|
StakeGiftIdText: giftID,
|
||||||
StakeCoin: gift.GetGiftPriceCoin(),
|
StakeCoin: stakeCoin,
|
||||||
Initiator: &gamev1.RoomRPSPlayer{UserId: req.GetUserId(), Gesture: gesture, JoinedAtMs: nowMS},
|
Initiator: &gamev1.RoomRPSPlayer{UserId: req.GetUserId(), Gesture: gesture, BalanceAfter: initiatorBalance, JoinedAtMs: nowMS},
|
||||||
SettlementStatus: SettlementPending,
|
SettlementStatus: SettlementPending,
|
||||||
TimeoutAtMs: now.Add(s.config.ChallengeTimeout).UnixMilli(),
|
TimeoutAtMs: now.Add(s.config.ChallengeTimeout).UnixMilli(),
|
||||||
CreatedAtMs: nowMS,
|
CreatedAtMs: nowMS,
|
||||||
@ -370,9 +390,45 @@ func (s *Service) AcceptChallenge(ctx context.Context, req *gamev1.AcceptRoomRPS
|
|||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
return nil, 0, xerr.New(xerr.Conflict, "room rps initiator can not accept self challenge")
|
return nil, 0, xerr.New(xerr.Conflict, "room rps initiator can not accept self challenge")
|
||||||
}
|
}
|
||||||
|
stakeCoin := challenge.GetStakeCoin()
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
challengerBalance, err := s.ensureCoinBalance(ctx, req.GetMeta().GetRequestId(), app, req.GetUserId(), stakeCoin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
challenge, ok = s.challenges[challengeID]
|
||||||
|
if !ok || challenge.GetAppCode() != app {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return nil, 0, xerr.New(xerr.NotFound, "room rps challenge not found")
|
||||||
|
}
|
||||||
|
now = s.now()
|
||||||
|
nowMS = now.UnixMilli()
|
||||||
|
if challenge.GetStatus() != StatusPending {
|
||||||
|
// 余额查询期间可能已有其他用户抢先应战;状态机必须以锁内二次校验为准。
|
||||||
|
s.mu.Unlock()
|
||||||
|
return nil, 0, xerr.New(xerr.Conflict, "room rps challenge is not pending")
|
||||||
|
}
|
||||||
|
if challenge.GetTimeoutAtMs() > 0 && nowMS > challenge.GetTimeoutAtMs() {
|
||||||
|
// 二次校验也要收敛超时单,避免余额查询耗时跨过 timeout 后仍然匹配成功。
|
||||||
|
challenge.Status = StatusTimeout
|
||||||
|
challenge.SettlementStatus = SettlementRefunded
|
||||||
|
challenge.UpdatedAtMs = nowMS
|
||||||
|
cloned := cloneChallenge(challenge)
|
||||||
|
s.mu.Unlock()
|
||||||
|
_ = s.publishChallengeEvent(ctx, eventExpired, cloned)
|
||||||
|
return nil, 0, xerr.New(xerr.Conflict, "room rps challenge is timeout")
|
||||||
|
}
|
||||||
|
if challenge.GetInitiator().GetUserId() == req.GetUserId() {
|
||||||
|
// 二次校验保留同一条权限边界,防止测试或未来接口在第一次检查后篡改挑战归属。
|
||||||
|
s.mu.Unlock()
|
||||||
|
return nil, 0, xerr.New(xerr.Conflict, "room rps initiator can not accept self challenge")
|
||||||
|
}
|
||||||
|
|
||||||
challenge.Status = StatusFinished
|
challenge.Status = StatusFinished
|
||||||
challenge.Challenger = &gamev1.RoomRPSPlayer{UserId: req.GetUserId(), Gesture: gesture, JoinedAtMs: nowMS}
|
challenge.Challenger = &gamev1.RoomRPSPlayer{UserId: req.GetUserId(), Gesture: gesture, BalanceAfter: challengerBalance, JoinedAtMs: nowMS}
|
||||||
challenge.MatchedAtMs = nowMS
|
challenge.MatchedAtMs = nowMS
|
||||||
challenge.RevealAtMs = now.Add(s.config.RevealCountdown).UnixMilli()
|
challenge.RevealAtMs = now.Add(s.config.RevealCountdown).UnixMilli()
|
||||||
challenge.SettledAtMs = nowMS
|
challenge.SettledAtMs = nowMS
|
||||||
@ -745,6 +801,34 @@ func (s *Service) roomRPSGiftCatalog(ctx context.Context, app string, giftIDs []
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) ensureCoinBalance(ctx context.Context, requestID string, app string, userID int64, stakeCoin int64) (int64, error) {
|
||||||
|
if stakeCoin <= 0 {
|
||||||
|
return 0, xerr.New(xerr.InvalidArgument, "room rps stake coin is invalid")
|
||||||
|
}
|
||||||
|
if s.wallet == nil {
|
||||||
|
// 没有 wallet-service 余额事实时不能放用户进入 PK;否则测试服/线上配置缺依赖会退化成免费 PK。
|
||||||
|
return 0, xerr.New(xerr.Unavailable, "room rps wallet client is not configured")
|
||||||
|
}
|
||||||
|
resp, err := s.wallet.GetBalances(ctx, &walletv1.GetBalancesRequest{
|
||||||
|
RequestId: strings.TrimSpace(requestID),
|
||||||
|
UserId: userID,
|
||||||
|
AppCode: appcode.Normalize(app),
|
||||||
|
AssetTypes: []string{"COIN"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for _, balance := range resp.GetBalances() {
|
||||||
|
if strings.EqualFold(balance.GetAssetType(), "COIN") {
|
||||||
|
if balance.GetAvailableAmount() >= stakeCoin {
|
||||||
|
return balance.GetAvailableAmount(), nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, xerr.New(xerr.InsufficientBalance, "coin balance is insufficient")
|
||||||
|
}
|
||||||
|
|
||||||
func roomRPSGiftDisplayName(gift *walletv1.GiftConfig) string {
|
func roomRPSGiftDisplayName(gift *walletv1.GiftConfig) string {
|
||||||
if gift == nil {
|
if gift == nil {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@ -129,7 +129,8 @@ func TestCreateChallengeMatchesStringGiftID(t *testing.T) {
|
|||||||
"Gifi-1": roomRPSWalletGift("Gifi-1", "Warrior", "https://cdn.example.test/warrior.png", "", "", 1),
|
"Gifi-1": roomRPSWalletGift("Gifi-1", "Warrior", "https://cdn.example.test/warrior.png", "", "", 1),
|
||||||
"lw1": roomRPSWalletGift("lw1", "Lucky Wheel", "https://cdn.example.test/lw1.png", "", "", 111),
|
"lw1": roomRPSWalletGift("lw1", "Lucky Wheel", "https://cdn.example.test/lw1.png", "", "", 111),
|
||||||
})
|
})
|
||||||
svc := New(Config{}, nil, nil, catalog)
|
wallet := newFakeRoomRPSWallet(map[int64]int64{42: 10})
|
||||||
|
svc := New(Config{}, nil, nil, catalog, wallet)
|
||||||
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
||||||
|
|
||||||
_, _, err := svc.UpdateConfig(context.Background(), "lalu", &gamev1.RoomRPSConfig{
|
_, _, err := svc.UpdateConfig(context.Background(), "lalu", &gamev1.RoomRPSConfig{
|
||||||
@ -161,6 +162,50 @@ func TestCreateChallengeMatchesStringGiftID(t *testing.T) {
|
|||||||
if challenge.GetStakeGiftIdText() != "Gifi-3" || challenge.GetStakeGiftId() != 0 || challenge.GetStakeCoin() != 1 {
|
if challenge.GetStakeGiftIdText() != "Gifi-3" || challenge.GetStakeGiftId() != 0 || challenge.GetStakeCoin() != 1 {
|
||||||
t.Fatalf("string gift challenge mismatch: %+v", challenge)
|
t.Fatalf("string gift challenge mismatch: %+v", challenge)
|
||||||
}
|
}
|
||||||
|
if challenge.GetInitiator().GetBalanceAfter() != 10 {
|
||||||
|
t.Fatalf("initiator balance snapshot mismatch: %+v", challenge.GetInitiator())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateChallengeRejectsInsufficientCoinBalance(t *testing.T) {
|
||||||
|
catalog := newFakeGiftCatalog(map[string]*walletv1.GiftConfig{
|
||||||
|
"Gifi-3": roomRPSWalletGift("Gifi-3", "Tiger", "https://cdn.example.test/tiger.png", "", "", 100),
|
||||||
|
"Gifi-2": roomRPSWalletGift("Gifi-2", "Cowboy", "https://cdn.example.test/cowboy.png", "", "", 200),
|
||||||
|
"Gifi-1": roomRPSWalletGift("Gifi-1", "Warrior", "https://cdn.example.test/warrior.png", "", "", 300),
|
||||||
|
"lw1": roomRPSWalletGift("lw1", "Lucky Wheel", "https://cdn.example.test/lw1.png", "", "", 400),
|
||||||
|
})
|
||||||
|
wallet := newFakeRoomRPSWallet(map[int64]int64{42: 99})
|
||||||
|
svc := New(Config{}, nil, nil, catalog, wallet)
|
||||||
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
||||||
|
|
||||||
|
_, _, err := svc.UpdateConfig(context.Background(), "lalu", &gamev1.RoomRPSConfig{
|
||||||
|
Status: "active",
|
||||||
|
ChallengeTimeoutMs: 600000,
|
||||||
|
RevealCountdownMs: 3000,
|
||||||
|
StakeGifts: []*gamev1.RoomRPSStakeGift{
|
||||||
|
{GiftIdText: "Gifi-3", Enabled: true, SortOrder: 1},
|
||||||
|
{GiftIdText: "Gifi-2", Enabled: true, SortOrder: 2},
|
||||||
|
{GiftIdText: "Gifi-1", Enabled: true, SortOrder: 3},
|
||||||
|
{GiftIdText: "lw1", Enabled: true, SortOrder: 4},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UpdateConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
||||||
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-create-low-balance"},
|
||||||
|
UserId: 42,
|
||||||
|
RoomId: "room-1",
|
||||||
|
GiftIdText: "Gifi-3",
|
||||||
|
Gesture: "rock",
|
||||||
|
})
|
||||||
|
if !xerr.IsCode(err, xerr.InsufficientBalance) {
|
||||||
|
t.Fatalf("CreateChallenge() error = %v, want InsufficientBalance", err)
|
||||||
|
}
|
||||||
|
if len(svc.challenges) != 0 {
|
||||||
|
t.Fatalf("insufficient balance must not create pending challenge: %+v", svc.challenges)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoomRPSConfigPersistsAcrossServiceInstances(t *testing.T) {
|
func TestRoomRPSConfigPersistsAcrossServiceInstances(t *testing.T) {
|
||||||
@ -214,7 +259,8 @@ func TestCreateChallengeUsesPersistentRoomRPSConfig(t *testing.T) {
|
|||||||
CreatedAtMs: 1000,
|
CreatedAtMs: 1000,
|
||||||
UpdatedAtMs: 2000,
|
UpdatedAtMs: 2000,
|
||||||
}
|
}
|
||||||
svc := New(Config{}, nil, nil, store)
|
wallet := newFakeRoomRPSWallet(map[int64]int64{42: 1000})
|
||||||
|
svc := New(Config{}, nil, nil, store, wallet)
|
||||||
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
||||||
|
|
||||||
challenge, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
challenge, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
||||||
@ -232,6 +278,70 @@ func TestCreateChallengeUsesPersistentRoomRPSConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAcceptChallengeRejectsInsufficientCoinBalance(t *testing.T) {
|
||||||
|
wallet := newFakeRoomRPSWallet(map[int64]int64{41: 1000, 42: 99})
|
||||||
|
svc := New(Config{}, nil, nil, wallet)
|
||||||
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
||||||
|
|
||||||
|
created, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
||||||
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-create"},
|
||||||
|
UserId: 41,
|
||||||
|
RoomId: "room-1",
|
||||||
|
GiftId: 10001,
|
||||||
|
Gesture: "rock",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateChallenge() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = svc.AcceptChallenge(context.Background(), &gamev1.AcceptRoomRPSChallengeRequest{
|
||||||
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-accept-low-balance"},
|
||||||
|
UserId: 42,
|
||||||
|
ChallengeId: created.GetChallengeId(),
|
||||||
|
Gesture: "paper",
|
||||||
|
})
|
||||||
|
if !xerr.IsCode(err, xerr.InsufficientBalance) {
|
||||||
|
t.Fatalf("AcceptChallenge() error = %v, want InsufficientBalance", err)
|
||||||
|
}
|
||||||
|
current, _, err := svc.GetChallenge(context.Background(), "lalu", created.GetChallengeId())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetChallenge() error = %v", err)
|
||||||
|
}
|
||||||
|
if current.GetStatus() != StatusPending || current.GetChallenger().GetUserId() != 0 {
|
||||||
|
t.Fatalf("insufficient balance must leave challenge pending without challenger: %+v", current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAcceptChallengeRecordsChallengerCoinBalance(t *testing.T) {
|
||||||
|
wallet := newFakeRoomRPSWallet(map[int64]int64{41: 1000, 42: 500})
|
||||||
|
svc := New(Config{}, nil, nil, wallet)
|
||||||
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
||||||
|
|
||||||
|
created, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
||||||
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-create"},
|
||||||
|
UserId: 41,
|
||||||
|
RoomId: "room-1",
|
||||||
|
GiftId: 10001,
|
||||||
|
Gesture: "rock",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateChallenge() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accepted, _, err := svc.AcceptChallenge(context.Background(), &gamev1.AcceptRoomRPSChallengeRequest{
|
||||||
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-accept"},
|
||||||
|
UserId: 42,
|
||||||
|
ChallengeId: created.GetChallengeId(),
|
||||||
|
Gesture: "paper",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AcceptChallenge() error = %v", err)
|
||||||
|
}
|
||||||
|
if accepted.GetStatus() != StatusFinished || accepted.GetChallenger().GetBalanceAfter() != 500 {
|
||||||
|
t.Fatalf("accepted challenge balance snapshot mismatch: %+v", accepted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fakeGiftCatalog struct {
|
type fakeGiftCatalog struct {
|
||||||
gifts map[string]*walletv1.GiftConfig
|
gifts map[string]*walletv1.GiftConfig
|
||||||
requests []*walletv1.ListGiftConfigsRequest
|
requests []*walletv1.ListGiftConfigsRequest
|
||||||
@ -249,6 +359,24 @@ func (f *fakeGiftCatalog) ListGiftConfigs(_ context.Context, req *walletv1.ListG
|
|||||||
return &walletv1.ListGiftConfigsResponse{Gifts: []*walletv1.GiftConfig{}, Total: 0}, nil
|
return &walletv1.ListGiftConfigsResponse{Gifts: []*walletv1.GiftConfig{}, Total: 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeRoomRPSWallet struct {
|
||||||
|
balances map[int64]int64
|
||||||
|
requests []*walletv1.GetBalancesRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFakeRoomRPSWallet(balances map[int64]int64) *fakeRoomRPSWallet {
|
||||||
|
return &fakeRoomRPSWallet{balances: balances}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeRoomRPSWallet) GetBalances(_ context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) {
|
||||||
|
f.requests = append(f.requests, req)
|
||||||
|
return &walletv1.GetBalancesResponse{Balances: []*walletv1.AssetBalance{{
|
||||||
|
AppCode: req.GetAppCode(),
|
||||||
|
AssetType: "COIN",
|
||||||
|
AvailableAmount: f.balances[req.GetUserId()],
|
||||||
|
}}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func roomRPSWalletGift(giftID string, name string, assetURL string, previewURL string, animationURL string, coinPrice int64) *walletv1.GiftConfig {
|
func roomRPSWalletGift(giftID string, name string, assetURL string, previewURL string, animationURL string, coinPrice int64) *walletv1.GiftConfig {
|
||||||
return &walletv1.GiftConfig{
|
return &walletv1.GiftConfig{
|
||||||
AppCode: "lalu",
|
AppCode: "lalu",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user