538 lines
21 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package grpc
import (
"context"
"time"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/domain/ledger"
)
// ListVipPackages 返回当前 VIP 和可购买包列表。
func (s *Server) ListVipPackages(ctx context.Context, req *walletv1.ListVipPackagesRequest) (*walletv1.ListVipPackagesResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
state, levels, err := s.svc.ListVipPackages(ctx, req.GetUserId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.ListVipPackagesResponse{
CurrentVip: vipToProto(state.EffectiveVip),
Packages: make([]*walletv1.VipLevel, 0, len(levels)),
State: vipStateToProto(state),
ProgramConfig: vipProgramConfigToProto(state.ProgramConfig),
}
for _, level := range levels {
resp.Packages = append(resp.Packages, vipLevelToProto(level))
}
return resp, nil
}
// GetMyVip 返回当前登录用户 VIP 状态。
func (s *Server) GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
state, err := s.svc.GetVipState(ctx, req.GetUserId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
// 旧 vip 字段保留为最终生效投影,付费会员原始事实由 state.paid_vip 明确返回。
return &walletv1.GetMyVipResponse{Vip: vipToProto(state.EffectiveVip), State: vipStateToProto(state)}, nil
}
// GetVipProgramConfig 返回按 App 隔离的状态机配置和最高等级权益目录。
func (s *Server) GetVipProgramConfig(ctx context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
config, benefits, err := s.svc.GetVipProgramConfig(ctx)
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.GetVipProgramConfigResponse{
Config: vipProgramConfigToProto(config),
Benefits: vipBenefitsToProto(benefits),
ServerTimeMs: time.Now().UnixMilli(),
}, nil
}
// CheckVipBenefit 供权益 owner 精确判断单项能力,不允许调用方退化成等级比较。
func (s *Server) CheckVipBenefit(ctx context.Context, req *walletv1.CheckVipBenefitRequest) (*walletv1.CheckVipBenefitResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
result, err := s.svc.CheckVipBenefit(ctx, req.GetUserId(), req.GetBenefitCode())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.CheckVipBenefitResponse{
Allowed: result.Allowed,
Benefit: vipBenefitToProto(result.Benefit),
State: vipStateToProto(result.State),
DenialReason: result.DenialReason,
EvaluatedAtMs: result.EvaluatedAtMS,
}, nil
}
// PurchaseVip 购买、续期或升级 VIP。
func (s *Server) PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
receipt, err := s.svc.PurchaseVip(ctx, ledger.PurchaseVipCommand{
AppCode: req.GetAppCode(),
CommandID: req.GetCommandId(),
UserID: req.GetUserId(),
Level: req.GetLevel(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.PurchaseVipResponse{
OrderId: receipt.OrderID,
TransactionId: receipt.TransactionID,
Vip: vipToProto(receipt.Vip),
CoinSpent: receipt.CoinSpent,
CoinBalanceAfter: receipt.CoinBalanceAfter,
RewardItems: vipRewardItemsToProto(receipt.RewardItems),
State: vipStateToProto(receipt.State),
}, nil
}
// GrantVip 统一处理活动和后台赠送 VIP。
func (s *Server) GrantVip(ctx context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
receipt, err := s.svc.GrantVip(ctx, ledger.GrantVipCommand{
AppCode: req.GetAppCode(),
CommandID: req.GetCommandId(),
TargetUserID: req.GetTargetUserId(),
Level: req.GetLevel(),
GrantSource: req.GetGrantSource(),
OperatorUserID: req.GetOperatorUserId(),
Reason: req.GetReason(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.GrantVipResponse{
TransactionId: receipt.TransactionID,
Vip: vipToProto(receipt.Vip),
RewardItems: vipRewardItemsToProto(receipt.RewardItems),
ServerTimeMs: time.Now().UnixMilli(),
State: vipStateToProto(receipt.State),
}, nil
}
// GrantVipTrialCard 发卡但不自动佩戴resource_id 省略时由钱包按 level 解析同 App 预置资源。
func (s *Server) GrantVipTrialCard(ctx context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
resourceID := int64(0)
if req.ResourceId != nil {
resourceID = req.GetResourceId()
}
receipt, err := s.svc.GrantVipTrialCard(ctx, ledger.GrantVipTrialCardCommand{
AppCode: req.GetAppCode(),
CommandID: req.GetCommandId(),
TargetUserID: req.GetTargetUserId(),
Level: req.GetLevel(),
DurationMS: req.GetDurationMs(),
ResourceID: resourceID,
GrantSource: req.GetGrantSource(),
OperatorUserID: req.GetOperatorUserId(),
Reason: req.GetReason(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.GrantVipTrialCardResponse{
TrialCard: vipTrialCardToProto(receipt.TrialCard),
State: vipStateToProto(receipt.State),
ServerTimeMs: receipt.ServerTimeMS,
}, nil
}
// EquipVipTrialCard 精确佩戴一个 entitlement同类型旧装备仅解除指针库存与到期时间保持不变。
func (s *Server) EquipVipTrialCard(ctx context.Context, req *walletv1.EquipVipTrialCardRequest) (*walletv1.EquipVipTrialCardResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
card, state, err := s.svc.EquipVipTrialCard(ctx, ledger.EquipVipTrialCardCommand{
AppCode: req.GetAppCode(),
RequestID: req.GetRequestId(),
UserID: req.GetUserId(),
EntitlementID: req.GetEntitlementId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.EquipVipTrialCardResponse{
TrialCard: vipTrialCardToProto(card),
State: vipStateToProto(state),
ServerTimeMs: time.Now().UnixMilli(),
}, nil
}
// UnequipVipTrialCard 卸下当前体验卡并返回回落后的最终 VIP 状态。
func (s *Server) UnequipVipTrialCard(ctx context.Context, req *walletv1.UnequipVipTrialCardRequest) (*walletv1.UnequipVipTrialCardResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
unequipped, state, err := s.svc.UnequipVipTrialCard(ctx, ledger.UnequipVipTrialCardCommand{
AppCode: req.GetAppCode(),
RequestID: req.GetRequestId(),
UserID: req.GetUserId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.UnequipVipTrialCardResponse{
Unequipped: unequipped,
State: vipStateToProto(state),
ServerTimeMs: time.Now().UnixMilli(),
}, nil
}
// ListAdminVipLevels 返回后台 VIP 配置。
func (s *Server) ListAdminVipLevels(ctx context.Context, req *walletv1.ListAdminVipLevelsRequest) (*walletv1.ListAdminVipLevelsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
config, levels, err := s.svc.ListAdminVipLevels(ctx)
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.ListAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli(), ProgramConfig: vipProgramConfigToProto(config)}
for _, level := range levels {
resp.Levels = append(resp.Levels, vipLevelToProto(level))
}
return resp, nil
}
// UpdateAdminVipLevels 保存后台 VIP 配置。
func (s *Server) UpdateAdminVipLevels(ctx context.Context, req *walletv1.UpdateAdminVipLevelsRequest) (*walletv1.UpdateAdminVipLevelsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
commands := make([]ledger.AdminVipLevelCommand, 0, len(req.GetLevels()))
for _, item := range req.GetLevels() {
if item == nil {
continue
}
commands = append(commands, ledger.AdminVipLevelCommand{
Level: item.GetLevel(),
Name: item.GetName(),
Status: item.GetStatus(),
PriceCoin: item.GetPriceCoin(),
DurationMS: item.GetDurationMs(),
RewardResourceGroupID: item.GetRewardResourceGroupId(),
RequiredRechargeCoinAmount: item.GetRequiredRechargeCoinAmount(),
SortOrder: item.GetSortOrder(),
Benefits: vipBenefitsFromProto(item.GetBenefits()),
})
}
config, levels, err := s.svc.UpdateAdminVipLevels(ctx, commands, req.GetOperatorUserId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.UpdateAdminVipLevelsResponse{Levels: make([]*walletv1.VipLevel, 0, len(levels)), ServerTimeMs: time.Now().UnixMilli(), ProgramConfig: vipProgramConfigToProto(config)}
for _, level := range levels {
resp.Levels = append(resp.Levels, vipLevelToProto(level))
}
return resp, nil
}
// UpdateAdminVipProgramConfig 保存 App 级策略,审计操作者只接受请求外层的服务端身份字段。
func (s *Server) UpdateAdminVipProgramConfig(ctx context.Context, req *walletv1.UpdateAdminVipProgramConfigRequest) (*walletv1.UpdateAdminVipProgramConfigResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
config := vipProgramConfigFromProto(req.GetConfig())
config.AppCode = req.GetAppCode()
updated, err := s.svc.UpdateAdminVipProgramConfig(ctx, ledger.AdminVipProgramConfigCommand{
Config: config,
OperatorUserID: req.GetOperatorUserId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.UpdateAdminVipProgramConfigResponse{Config: vipProgramConfigToProto(updated), ServerTimeMs: time.Now().UnixMilli()}, nil
}
// UpdateMyVipSettings 保留 proto optional 的 partial update 语义;未提交字段不能被零值 false 覆盖。
func (s *Server) UpdateMyVipSettings(ctx context.Context, req *walletv1.UpdateMyVipSettingsRequest) (*walletv1.UpdateMyVipSettingsResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
var roomEntryNoticeEnabled *bool
if req.RoomEntryNoticeEnabled != nil {
value := req.GetRoomEntryNoticeEnabled()
roomEntryNoticeEnabled = &value
}
var onlineGlobalNoticeEnabled *bool
if req.OnlineGlobalNoticeEnabled != nil {
value := req.GetOnlineGlobalNoticeEnabled()
onlineGlobalNoticeEnabled = &value
}
settings, err := s.svc.UpdateVipUserSettings(ctx, ledger.UpdateVipUserSettingsCommand{
AppCode: req.GetAppCode(), UserID: req.GetUserId(),
RoomEntryNoticeEnabled: roomEntryNoticeEnabled, OnlineGlobalNoticeEnabled: onlineGlobalNoticeEnabled,
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.UpdateMyVipSettingsResponse{
Settings: vipUserSettingsToProto(settings), ServerTimeMs: time.Now().UTC().UnixMilli(),
}, nil
}
// GetMyCurrentVipDailyCoinRebate 返回当前 UTC 日可领取/已领取事实;未生成以 found=false 表达。
func (s *Server) GetMyCurrentVipDailyCoinRebate(ctx context.Context, req *walletv1.GetMyCurrentVipDailyCoinRebateRequest) (*walletv1.GetMyCurrentVipDailyCoinRebateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
rebate, found, err := s.svc.GetMyCurrentVipDailyCoinRebate(ctx, req.GetUserId())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
response := &walletv1.GetMyCurrentVipDailyCoinRebateResponse{Found: found, ServerTimeMs: time.Now().UTC().UnixMilli()}
if found {
response.Rebate = vipDailyCoinRebateToProto(rebate)
}
return response, nil
}
// ListMyVipDailyCoinRebateStatuses 同时支持历史分页和通知按 rebate_ids 精确恢复repository 始终附加 user_id 条件。
func (s *Server) ListMyVipDailyCoinRebateStatuses(ctx context.Context, req *walletv1.ListMyVipDailyCoinRebateStatusesRequest) (*walletv1.ListMyVipDailyCoinRebateStatusesResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
items, total, err := s.svc.ListMyVipDailyCoinRebateStatuses(ctx, ledger.ListVipDailyCoinRebateStatusesQuery{
AppCode: req.GetAppCode(), UserID: req.GetUserId(), Page: int(req.GetPage()),
PageSize: int(req.GetPageSize()), RebateIDs: append([]string(nil), req.GetRebateIds()...),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
response := &walletv1.ListMyVipDailyCoinRebateStatusesResponse{
Rebates: make([]*walletv1.VipDailyCoinRebate, 0, len(items)), Total: total,
ServerTimeMs: time.Now().UTC().UnixMilli(),
}
for _, item := range items {
response.Rebates = append(response.Rebates, vipDailyCoinRebateToProto(item))
}
return response, nil
}
// ClaimVipDailyCoinRebate 不接收金额wallet owner 从锁内返现行读取并返回稳定账后余额。
func (s *Server) ClaimVipDailyCoinRebate(ctx context.Context, req *walletv1.ClaimVipDailyCoinRebateRequest) (*walletv1.ClaimVipDailyCoinRebateResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
receipt, err := s.svc.ClaimVipDailyCoinRebate(ctx, ledger.ClaimVipDailyCoinRebateCommand{
AppCode: req.GetAppCode(), CommandID: req.GetCommandId(), UserID: req.GetUserId(), RebateID: req.GetRebateId(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.ClaimVipDailyCoinRebateResponse{
Rebate: vipDailyCoinRebateToProto(receipt.Rebate), TransactionId: receipt.TransactionID,
CoinBalance: balanceToProto(receipt.CoinBalance), ServerTimeMs: time.Now().UTC().UnixMilli(),
}, nil
}
func vipToProto(vip ledger.UserVip) *walletv1.UserVip {
return &walletv1.UserVip{
UserId: vip.UserID,
Level: vip.Level,
Name: vip.Name,
Active: vip.Active,
StartedAtMs: vip.StartedAtMS,
ExpiresAtMs: vip.ExpiresAtMS,
UpdatedAtMs: vip.UpdatedAtMS,
ProgramType: vip.ProgramType,
ConfigVersion: vip.ConfigVersion,
}
}
func vipDailyCoinRebateRunToProto(run ledger.VipDailyCoinRebateRun) *walletv1.VipDailyCoinRebateRun {
return &walletv1.VipDailyCoinRebateRun{
RunId: run.RunID, TaskDay: run.TaskDay, DayStartMs: run.DayStartMS, DayEndMs: run.DayEndMS,
ConfigVersion: run.ConfigVersion, LevelAmountsJson: run.LevelAmountsJSON, Status: run.Status,
LastUserId: run.LastUserID, ScannedCount: run.ScannedCount, CreatedCount: run.CreatedCount,
CreatedAtMs: run.CreatedAtMS, UpdatedAtMs: run.UpdatedAtMS, CompletedAtMs: run.CompletedAtMS,
}
}
func vipDailyCoinRebateToProto(rebate ledger.VipDailyCoinRebate) *walletv1.VipDailyCoinRebate {
if rebate.RebateID == "" {
return nil
}
return &walletv1.VipDailyCoinRebate{
RebateId: rebate.RebateID, UserId: rebate.UserID, TaskDay: rebate.TaskDay,
VipLevel: rebate.VipLevel, VipName: rebate.VipName, CoinAmount: rebate.CoinAmount,
Status: rebate.Status, AvailableAtMs: rebate.AvailableAtMS, ExpiresAtMs: rebate.ExpiresAtMS,
ConfigVersion: rebate.ConfigVersion, ClaimedAtMs: rebate.ClaimedAtMS,
WalletTransactionId: rebate.WalletTransactionID, CreatedAtMs: rebate.CreatedAtMS, UpdatedAtMs: rebate.UpdatedAtMS,
}
}
func vipUserSettingsToProto(settings ledger.VipUserSettings) *walletv1.VipUserSettings {
return &walletv1.VipUserSettings{
AppCode: settings.AppCode, UserId: settings.UserID,
RoomEntryNoticeEnabled: settings.RoomEntryNoticeEnabled,
OnlineGlobalNoticeEnabled: settings.OnlineGlobalNoticeEnabled,
UpdatedAtMs: settings.UpdatedAtMS,
}
}
func vipLevelToProto(level ledger.VipLevel) *walletv1.VipLevel {
return &walletv1.VipLevel{
Level: level.Level,
Name: level.Name,
Status: level.Status,
PriceCoin: level.PriceCoin,
DurationMs: level.DurationMS,
RewardResourceGroupId: level.RewardResourceGroupID,
RewardItems: vipRewardItemsToProto(level.RewardItems),
CanPurchase: level.CanPurchase,
SortOrder: level.SortOrder,
RechargeGateRequired: level.RechargeGateRequired,
RequiredRechargeCoinAmount: level.RequiredRechargeCoinAmount,
UserRechargeCoinAmount: level.UserRechargeCoinAmount,
PurchaseLockedReason: level.PurchaseLockedReason,
CreatedAtMs: level.CreatedAtMS,
UpdatedAtMs: level.UpdatedAtMS,
Benefits: vipBenefitsToProto(level.Benefits),
ConfigVersion: level.ConfigVersion,
}
}
func vipProgramConfigToProto(config ledger.VipProgramConfig) *walletv1.VipProgramConfig {
return &walletv1.VipProgramConfig{
AppCode: config.AppCode,
ProgramType: config.ProgramType,
LevelCount: config.LevelCount,
SameLevelExpiryPolicy: config.SameLevelExpiryPolicy,
UpgradeExpiryPolicy: config.UpgradeExpiryPolicy,
DowngradePurchasePolicy: config.DowngradePurchasePolicy,
BenefitInheritancePolicy: config.BenefitInheritancePolicy,
GrantMode: config.GrantMode,
TrialCardEnabled: config.TrialCardEnabled,
Status: config.Status,
ConfigVersion: config.ConfigVersion,
UpdatedByAdminId: config.UpdatedByAdminID,
CreatedAtMs: config.CreatedAtMS,
UpdatedAtMs: config.UpdatedAtMS,
}
}
func vipProgramConfigFromProto(config *walletv1.VipProgramConfig) ledger.VipProgramConfig {
if config == nil {
return ledger.VipProgramConfig{}
}
return ledger.VipProgramConfig{
AppCode: config.GetAppCode(),
ProgramType: config.GetProgramType(),
LevelCount: config.GetLevelCount(),
SameLevelExpiryPolicy: config.GetSameLevelExpiryPolicy(),
UpgradeExpiryPolicy: config.GetUpgradeExpiryPolicy(),
DowngradePurchasePolicy: config.GetDowngradePurchasePolicy(),
BenefitInheritancePolicy: config.GetBenefitInheritancePolicy(),
GrantMode: config.GetGrantMode(),
TrialCardEnabled: config.GetTrialCardEnabled(),
Status: config.GetStatus(),
}
}
func vipBenefitToProto(benefit ledger.VipBenefit) *walletv1.VipBenefit {
if benefit.BenefitCode == "" {
return nil
}
return &walletv1.VipBenefit{
BenefitCode: benefit.BenefitCode,
Name: benefit.Name,
BenefitType: benefit.BenefitType,
UnlockLevel: benefit.UnlockLevel,
Status: benefit.Status,
TrialEnabled: benefit.TrialEnabled,
ResourceId: benefit.ResourceID,
ResourceType: benefit.ResourceType,
ExecutionScope: benefit.ExecutionScope,
AutoEquip: benefit.AutoEquip,
SortOrder: benefit.SortOrder,
MetadataJson: benefit.MetadataJSON,
CreatedAtMs: benefit.CreatedAtMS,
UpdatedAtMs: benefit.UpdatedAtMS,
}
}
func vipBenefitsToProto(benefits []ledger.VipBenefit) []*walletv1.VipBenefit {
items := make([]*walletv1.VipBenefit, 0, len(benefits))
for _, benefit := range benefits {
if item := vipBenefitToProto(benefit); item != nil {
items = append(items, item)
}
}
return items
}
func vipBenefitsFromProto(benefits []*walletv1.VipBenefit) []ledger.VipBenefit {
items := make([]ledger.VipBenefit, 0, len(benefits))
for _, benefit := range benefits {
if benefit == nil {
continue
}
items = append(items, ledger.VipBenefit{
BenefitCode: benefit.GetBenefitCode(),
Name: benefit.GetName(),
BenefitType: benefit.GetBenefitType(),
UnlockLevel: benefit.GetUnlockLevel(),
Status: benefit.GetStatus(),
TrialEnabled: benefit.GetTrialEnabled(),
ResourceID: benefit.GetResourceId(),
ResourceType: benefit.GetResourceType(),
ExecutionScope: benefit.GetExecutionScope(),
AutoEquip: benefit.GetAutoEquip(),
SortOrder: benefit.GetSortOrder(),
MetadataJSON: benefit.GetMetadataJson(),
})
}
return items
}
func vipTrialCardToProto(card ledger.VipTrialCard) *walletv1.VipTrialCard {
if card.TrialCardID == "" {
return nil
}
return &walletv1.VipTrialCard{
TrialCardId: card.TrialCardID,
EntitlementId: card.EntitlementID,
ResourceId: card.ResourceID,
UserId: card.UserID,
Level: card.Level,
Name: card.Name,
Status: card.Status,
Equipped: card.Equipped,
DurationMs: card.DurationMS,
EffectiveAtMs: card.EffectiveAtMS,
ExpiresAtMs: card.ExpiresAtMS,
RemainingDurationMs: card.RemainingDurationMS,
GrantSource: card.GrantSource,
SourceGrantId: card.SourceGrantID,
CreatedAtMs: card.CreatedAtMS,
UpdatedAtMs: card.UpdatedAtMS,
}
}
func vipStateToProto(state ledger.VipState) *walletv1.VipState {
return &walletv1.VipState{
PaidVip: vipToProto(state.PaidVip),
EquippedTrialCard: func() *walletv1.VipTrialCard {
if state.EquippedTrialCard == nil {
return nil
}
return vipTrialCardToProto(*state.EquippedTrialCard)
}(),
EffectiveVip: vipToProto(state.EffectiveVip),
EffectiveSource: state.EffectiveSource,
EffectiveBenefits: vipBenefitsToProto(state.EffectiveBenefits),
EvaluatedAtMs: state.EvaluatedAtMS,
ProgramConfig: vipProgramConfigToProto(state.ProgramConfig),
UserSettings: vipUserSettingsToProto(state.UserSettings),
}
}
func vipRewardItemsToProto(items []ledger.VipRewardItem) []*walletv1.VipRewardItem {
resp := make([]*walletv1.VipRewardItem, 0, len(items))
for _, item := range items {
resp = append(resp, &walletv1.VipRewardItem{
ResourceId: item.ResourceID,
ResourceCode: item.ResourceCode,
ResourceType: item.ResourceType,
Name: item.Name,
Quantity: item.Quantity,
ExpiresAtMs: item.ExpiresAtMS,
AssetUrl: item.AssetURL,
PreviewUrl: item.PreviewURL,
AnimationUrl: item.AnimationURL,
})
}
return resp
}