423 lines
19 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 wallet
import (
"context"
"strings"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/domain/ledger"
resourcedomain "hyapp/services/wallet-service/internal/domain/resource"
)
func (s *Service) ListResources(ctx context.Context, query resourcedomain.ListResourcesQuery) ([]resourcedomain.Resource, int64, error) {
if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
if strings.TrimSpace(query.ResourceType) != "" && !resourcedomain.ValidResourceType(query.ResourceType) {
return nil, 0, xerr.New(xerr.InvalidArgument, "resource_type is invalid")
}
return s.repository.ListResources(ctx, query)
}
func (s *Service) GetResource(ctx context.Context, appCode string, resourceID int64) (resourcedomain.Resource, error) {
if resourceID <= 0 {
return resourcedomain.Resource{}, xerr.New(xerr.InvalidArgument, "resource_id is required")
}
if s.repository == nil {
return resourcedomain.Resource{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, appcode.Normalize(appCode))
return s.repository.GetResource(ctx, resourceID)
}
func (s *Service) CreateResource(ctx context.Context, command resourcedomain.ResourceCommand) (resourcedomain.Resource, error) {
if s.repository == nil {
return resourcedomain.Resource{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreateResource(ctx, command)
}
func (s *Service) UpdateResource(ctx context.Context, command resourcedomain.ResourceCommand) (resourcedomain.Resource, error) {
if s.repository == nil {
return resourcedomain.Resource{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.UpdateResource(ctx, command)
}
func (s *Service) SetResourceStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.Resource, error) {
if s.repository == nil {
return resourcedomain.Resource{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.SetResourceStatus(ctx, command)
}
func (s *Service) DeleteResource(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.Resource, error) {
if s.repository == nil {
return resourcedomain.Resource{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.DeleteResource(ctx, command)
}
func (s *Service) BatchDeleteResources(ctx context.Context, command resourcedomain.BatchDeleteResourcesCommand) ([]resourcedomain.Resource, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.BatchDeleteResources(ctx, command)
}
func (s *Service) ListResourceGroups(ctx context.Context, query resourcedomain.ListResourceGroupsQuery) ([]resourcedomain.ResourceGroup, int64, error) {
if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.ListResourceGroups(ctx, query)
}
func (s *Service) GetResourceGroup(ctx context.Context, appCode string, groupID int64) (resourcedomain.ResourceGroup, error) {
if groupID <= 0 {
return resourcedomain.ResourceGroup{}, xerr.New(xerr.InvalidArgument, "group_id is required")
}
if s.repository == nil {
return resourcedomain.ResourceGroup{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, appcode.Normalize(appCode))
return s.repository.GetResourceGroup(ctx, groupID)
}
func (s *Service) CreateResourceGroup(ctx context.Context, command resourcedomain.ResourceGroupCommand) (resourcedomain.ResourceGroup, error) {
if s.repository == nil {
return resourcedomain.ResourceGroup{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreateResourceGroup(ctx, command)
}
func (s *Service) UpdateResourceGroup(ctx context.Context, command resourcedomain.ResourceGroupCommand) (resourcedomain.ResourceGroup, error) {
if s.repository == nil {
return resourcedomain.ResourceGroup{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.UpdateResourceGroup(ctx, command)
}
func (s *Service) SetResourceGroupStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.ResourceGroup, error) {
if s.repository == nil {
return resourcedomain.ResourceGroup{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.SetResourceGroupStatus(ctx, command)
}
func (s *Service) ListGiftConfigs(ctx context.Context, query resourcedomain.ListGiftConfigsQuery) ([]resourcedomain.GiftConfig, int64, error) {
if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
if query.FilterRegion && query.RegionID < 0 {
return nil, 0, xerr.New(xerr.InvalidArgument, "region_id is invalid")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.ListGiftConfigs(ctx, query)
}
func (s *Service) ListGiftTypeConfigs(ctx context.Context, query resourcedomain.ListGiftTypeConfigsQuery) ([]resourcedomain.GiftTypeConfig, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.ListGiftTypeConfigs(ctx, query)
}
func (s *Service) GetGiftCatalogVersion(ctx context.Context, appCode string) (int64, error) {
if s.repository == nil {
return 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
return s.repository.GetGiftCatalogVersion(ctx, appCode)
}
func (s *Service) CreateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) {
if s.repository == nil {
return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.CreateGiftConfig(ctx, command)
}
func (s *Service) BatchCreateGiftConfigs(ctx context.Context, appCode string, commands []resourcedomain.GiftConfigCommand, operatorUserID int64) ([]resourcedomain.GiftConfig, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
if len(commands) == 0 {
return nil, xerr.New(xerr.InvalidArgument, "gift configs are required")
}
batchCommands := make([]resourcedomain.GiftConfigCommand, 0, len(commands))
for _, command := range commands {
command.AppCode = appCode
if command.OperatorUserID == 0 {
command.OperatorUserID = operatorUserID
}
batchCommands = append(batchCommands, command)
}
return s.repository.BatchCreateGiftConfigs(ctx, batchCommands)
}
func (s *Service) UpdateGiftConfig(ctx context.Context, command resourcedomain.GiftConfigCommand) (resourcedomain.GiftConfig, error) {
if s.repository == nil {
return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.UpdateGiftConfig(ctx, command)
}
func (s *Service) SetGiftConfigStatus(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) {
if s.repository == nil {
return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.SetGiftConfigStatus(ctx, command)
}
func (s *Service) DeleteGiftConfig(ctx context.Context, command resourcedomain.StatusCommand) (resourcedomain.GiftConfig, error) {
if s.repository == nil {
return resourcedomain.GiftConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.DeleteGiftConfig(ctx, command)
}
func (s *Service) UpsertGiftTypeConfig(ctx context.Context, command resourcedomain.GiftTypeConfigCommand) (resourcedomain.GiftTypeConfig, error) {
if s.repository == nil {
return resourcedomain.GiftTypeConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.UpsertGiftTypeConfig(ctx, command)
}
func (s *Service) GrantResource(ctx context.Context, command resourcedomain.GrantResourceCommand) (resourcedomain.ResourceGrant, error) {
if s.repository == nil {
return resourcedomain.ResourceGrant{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.GrantResource(ctx, command)
}
func (s *Service) GrantResourceGroup(ctx context.Context, command resourcedomain.GrantResourceGroupCommand) (resourcedomain.ResourceGrant, error) {
if s.repository == nil {
return resourcedomain.ResourceGrant{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.GrantResourceGroup(ctx, command)
}
func (s *Service) RevokeResourceGrant(ctx context.Context, command resourcedomain.RevokeResourceGrantCommand) (resourcedomain.ResourceGrant, error) {
if s.repository == nil {
return resourcedomain.ResourceGrant{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.RevokeResourceGrant(ctx, command)
}
func (s *Service) ListUserResources(ctx context.Context, query resourcedomain.ListUserResourcesQuery) ([]resourcedomain.UserResourceEntitlement, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.ListUserResources(ctx, query)
}
func (s *Service) EquipUserResource(ctx context.Context, command resourcedomain.EquipUserResourceCommand) (resourcedomain.UserResourceEntitlement, error) {
if s.repository == nil {
return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
if command.UserID <= 0 || command.ResourceID <= 0 {
return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.InvalidArgument, "user_id and resource_id are required")
}
resource, err := s.repository.GetResource(ctx, command.ResourceID)
if err != nil {
return resourcedomain.UserResourceEntitlement{}, err
}
if resourcedomain.NormalizeResourceType(resource.ResourceType) == resourcedomain.TypeVIPTrialCard {
// 体验卡在 UI 上仍是普通背包物品,但其装备动作会改变最终 VIP 权益。
// 因此通用入口必须委托专用状态机,不能直接写 equipment 而绕过 App 开关和 VipEffectiveChanged outbox。
command.RequestID = strings.TrimSpace(command.RequestID)
command.EntitlementID = strings.TrimSpace(command.EntitlementID)
if command.RequestID == "" || command.EntitlementID == "" {
return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.InvalidArgument, "request_id and entitlement_id are required for vip_trial_card")
}
items, listErr := s.repository.ListUserResources(ctx, resourcedomain.ListUserResourcesQuery{
AppCode: command.AppCode,
UserID: command.UserID,
ResourceType: resourcedomain.TypeVIPTrialCard,
ActiveOnly: true,
})
if listErr != nil {
return resourcedomain.UserResourceEntitlement{}, listErr
}
var selected *resourcedomain.UserResourceEntitlement
for index := range items {
if items[index].EntitlementID == command.EntitlementID && items[index].ResourceID == command.ResourceID {
selected = &items[index]
break
}
}
if selected == nil {
return resourcedomain.UserResourceEntitlement{}, xerr.New(xerr.NotFound, "vip trial card entitlement not found")
}
if _, _, err := s.EquipVipTrialCard(ctx, ledger.EquipVipTrialCardCommand{
AppCode: command.AppCode,
RequestID: command.RequestID,
UserID: command.UserID,
EntitlementID: command.EntitlementID,
}); err != nil {
return resourcedomain.UserResourceEntitlement{}, err
}
// 专用事务已经再次校验 entitlement 并提交装备/outbox这里复用预读完整资源投影满足通用 API 返回契约。
selected.Equipped = true
return *selected, nil
}
return s.repository.EquipUserResource(ctx, command)
}
func (s *Service) UnequipUserResource(ctx context.Context, command resourcedomain.UnequipUserResourceCommand) (resourcedomain.UnequipUserResourceResult, error) {
if s.repository == nil {
return resourcedomain.UnequipUserResourceResult{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
command.ResourceType = resourcedomain.NormalizeResourceType(command.ResourceType)
if command.ResourceType == resourcedomain.TypeVIPTrialCard {
// 和装备相同,通用卸下必须发布 VIP 有效态变化,不能只删除通用装备指针。
unequipped, state, err := s.UnequipVipTrialCard(ctx, ledger.UnequipVipTrialCardCommand{
AppCode: command.AppCode,
RequestID: strings.TrimSpace(command.RequestID),
UserID: command.UserID,
})
if err != nil {
return resourcedomain.UnequipUserResourceResult{}, err
}
return resourcedomain.UnequipUserResourceResult{
ResourceType: command.ResourceType,
Unequipped: unequipped,
UpdatedAtMS: state.EvaluatedAtMS,
}, nil
}
return s.repository.UnequipUserResource(ctx, command)
}
func (s *Service) BatchGetUserEquippedResources(ctx context.Context, query resourcedomain.BatchGetUserEquippedResourcesQuery) ([]resourcedomain.UserEquippedResources, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.BatchGetUserEquippedResources(ctx, query)
}
func (s *Service) ListResourceGrants(ctx context.Context, query resourcedomain.ListResourceGrantsQuery) ([]resourcedomain.ResourceGrant, int64, error) {
if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.ListResourceGrants(ctx, query)
}
func (s *Service) ListResourceShopItems(ctx context.Context, query resourcedomain.ListResourceShopItemsQuery) ([]resourcedomain.ResourceShopItem, int64, error) {
if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
if strings.TrimSpace(query.ResourceType) != "" {
resourceType := resourcedomain.NormalizeResourceType(query.ResourceType)
if !resourcedomain.ResourceTypeSellableInShop(resourceType) {
return nil, 0, xerr.New(xerr.InvalidArgument, "resource_type is invalid")
}
query.ResourceType = resourceType
}
return s.repository.ListResourceShopItems(ctx, query)
}
func (s *Service) UpsertResourceShopItems(ctx context.Context, command resourcedomain.ResourceShopItemsCommand) ([]resourcedomain.ResourceShopItem, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.UpsertResourceShopItems(ctx, command)
}
func (s *Service) SetResourceShopItemStatus(ctx context.Context, command resourcedomain.ResourceShopItemStatusCommand) (resourcedomain.ResourceShopItem, error) {
if s.repository == nil {
return resourcedomain.ResourceShopItem{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.SetResourceShopItemStatus(ctx, command)
}
func (s *Service) ListResourceShopPurchaseOrders(ctx context.Context, query resourcedomain.ListResourceShopPurchaseOrdersQuery) ([]resourcedomain.ResourceShopPurchaseOrder, int64, error) {
if s.repository == nil {
return nil, 0, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
if strings.TrimSpace(query.ResourceType) != "" {
resourceType := resourcedomain.NormalizeResourceType(query.ResourceType)
if !resourcedomain.ResourceTypeSellableInShop(resourceType) {
return nil, 0, xerr.New(xerr.InvalidArgument, "resource_type is invalid")
}
query.ResourceType = resourceType
}
return s.repository.ListResourceShopPurchaseOrders(ctx, query)
}
func (s *Service) PurchaseResourceShopItem(ctx context.Context, command resourcedomain.ResourceShopPurchaseCommand) (resourcedomain.ResourceShopPurchaseReceipt, error) {
if strings.TrimSpace(command.CommandID) == "" || command.UserID <= 0 || command.ShopItemID <= 0 {
return resourcedomain.ResourceShopPurchaseReceipt{}, xerr.New(xerr.InvalidArgument, "resource shop purchase command is incomplete")
}
if len(command.CommandID) > 128 {
return resourcedomain.ResourceShopPurchaseReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long")
}
if s.repository == nil {
return resourcedomain.ResourceShopPurchaseReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
command.CommandID = strings.TrimSpace(command.CommandID)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.PurchaseResourceShopItem(ctx, command)
}