246 lines
12 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"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/domain/ledger"
"strings"
)
// ListVipPackages 返回同一配置快照下的最终 VIP 状态和可购买套餐。
func (s *Service) ListVipPackages(ctx context.Context, userID int64) (ledger.VipState, []ledger.VipLevel, error) {
if userID <= 0 {
return ledger.VipState{}, nil, xerr.New(xerr.InvalidArgument, "user_id is required")
}
if s.repository == nil {
return ledger.VipState{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
return s.repository.ListVipPackages(ctx, userID)
}
// GetMyVip 返回当前用户会员状态;过期会员会按 active=false 投影。
func (s *Service) GetMyVip(ctx context.Context, userID int64) (ledger.UserVip, error) {
if userID <= 0 {
return ledger.UserVip{}, xerr.New(xerr.InvalidArgument, "user_id is required")
}
if s.repository == nil {
return ledger.UserVip{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
return s.repository.GetMyVip(ctx, userID)
}
// GetVipProgramConfig 返回当前 App 的完整 VIP 规则和权益目录;不存在配置时直接失败,
// 避免业务在缺少迁移或错误 app_code 时悄悄回退到另一套体系。
func (s *Service) GetVipProgramConfig(ctx context.Context) (ledger.VipProgramConfig, []ledger.VipBenefit, error) {
if s.repository == nil {
return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
return s.repository.GetVipProgramConfig(ctx)
}
// GetVipState 统一返回付费、体验卡和最终生效状态,客户端与 owner service 都不能自行按等级合并。
func (s *Service) GetVipState(ctx context.Context, userID int64) (ledger.VipState, error) {
if userID <= 0 {
return ledger.VipState{}, xerr.New(xerr.InvalidArgument, "user_id is required")
}
if s.repository == nil {
return ledger.VipState{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
return s.repository.GetVipState(ctx, userID)
}
// CheckVipBenefit 是房间、用户资料和通知 owner 执行单项权益时的服务端判定入口。
func (s *Service) CheckVipBenefit(ctx context.Context, userID int64, benefitCode string) (ledger.CheckVipBenefitResult, error) {
benefitCode = strings.ToLower(strings.TrimSpace(benefitCode))
if userID <= 0 || benefitCode == "" {
return ledger.CheckVipBenefitResult{}, xerr.New(xerr.InvalidArgument, "user_id and benefit_code are required")
}
if s.repository == nil {
return ledger.CheckVipBenefitResult{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
return s.repository.CheckVipBenefit(ctx, userID, benefitCode)
}
// PurchaseVip 执行 VIP 购买、升级或续期;降级由 repository 在锁内按当前会员状态拒绝。
func (s *Service) PurchaseVip(ctx context.Context, command ledger.PurchaseVipCommand) (ledger.PurchaseVipReceipt, error) {
command.CommandID = strings.TrimSpace(command.CommandID)
if command.CommandID == "" || command.UserID <= 0 || command.Level <= 0 {
return ledger.PurchaseVipReceipt{}, xerr.New(xerr.InvalidArgument, "vip purchase command is incomplete")
}
if len(command.CommandID) > 128 {
return ledger.PurchaseVipReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long")
}
if s.repository == nil {
return ledger.PurchaseVipReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.PurchaseVip(ctx, command)
}
// GrantVip 是活动和后台赠送 VIP 的统一入口;购买仍走 PurchaseVip 完成扣费。
func (s *Service) GrantVip(ctx context.Context, command ledger.GrantVipCommand) (ledger.GrantVipReceipt, error) {
command.CommandID = strings.TrimSpace(command.CommandID)
if command.CommandID == "" || command.TargetUserID <= 0 || command.Level <= 0 {
return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "vip grant command is incomplete")
}
if len(command.CommandID) > 128 {
return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long")
}
command.GrantSource = ledger.NormalizeVipGrantSource(command.GrantSource)
switch command.GrantSource {
case ledger.VipGrantSourceAdmin, ledger.VipGrantSourceManagerCenter:
if command.OperatorUserID <= 0 {
return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "operator_user_id is required")
}
case ledger.VipGrantSourceActivity:
if command.OperatorUserID < 0 {
return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "operator_user_id is invalid")
}
default:
return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "vip grant_source is invalid")
}
command.Reason = strings.TrimSpace(command.Reason)
if command.Reason == "" {
command.Reason = command.GrantSource
}
if len(command.Reason) > 256 {
return ledger.GrantVipReceipt{}, xerr.New(xerr.InvalidArgument, "reason is too long")
}
if s.repository == nil {
return ledger.GrantVipReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.GrantVip(ctx, command)
}
// GrantVipTrialCard 发放独立绝对计时的背包卡片;发卡不自动佩戴,避免后台操作改变用户当前展示。
func (s *Service) GrantVipTrialCard(ctx context.Context, command ledger.GrantVipTrialCardCommand) (ledger.GrantVipTrialCardReceipt, error) {
command.CommandID = strings.TrimSpace(command.CommandID)
command.Reason = strings.TrimSpace(command.Reason)
command.GrantSource = ledger.NormalizeVipGrantSource(command.GrantSource)
if command.CommandID == "" || command.TargetUserID <= 0 || command.Level <= 0 || command.DurationMS <= 0 {
return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "vip trial card command is incomplete")
}
if len(command.CommandID) > 128 || len(command.Reason) > 256 || command.ResourceID < 0 {
return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "vip trial card command is invalid")
}
switch command.GrantSource {
case ledger.VipGrantSourceAdmin, ledger.VipGrantSourceManagerCenter:
if command.OperatorUserID <= 0 {
return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "operator_user_id is required")
}
case ledger.VipGrantSourceActivity:
if command.OperatorUserID < 0 {
return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "operator_user_id is invalid")
}
default:
return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.InvalidArgument, "vip grant_source is invalid")
}
if command.Reason == "" {
command.Reason = command.GrantSource
}
if s.repository == nil {
return ledger.GrantVipTrialCardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.GrantVipTrialCard(ctx, command)
}
// EquipVipTrialCard 只切换通用装备指针,任一卡片的 effective/expires 时间都不会变化。
func (s *Service) EquipVipTrialCard(ctx context.Context, command ledger.EquipVipTrialCardCommand) (ledger.VipTrialCard, ledger.VipState, error) {
command.RequestID = strings.TrimSpace(command.RequestID)
command.EntitlementID = strings.TrimSpace(command.EntitlementID)
if command.RequestID == "" || command.UserID <= 0 || command.EntitlementID == "" {
return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.InvalidArgument, "vip trial equip command is incomplete")
}
if len(command.RequestID) > 128 || len(command.EntitlementID) > 96 {
return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.InvalidArgument, "vip trial equip command is invalid")
}
if s.repository == nil {
return ledger.VipTrialCard{}, ledger.VipState{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.EquipVipTrialCard(ctx, command)
}
// UnequipVipTrialCard 清理当前体验卡装备记录,最终状态立即回落到仍有效的付费会员。
func (s *Service) UnequipVipTrialCard(ctx context.Context, command ledger.UnequipVipTrialCardCommand) (bool, ledger.VipState, error) {
command.RequestID = strings.TrimSpace(command.RequestID)
if command.RequestID == "" || command.UserID <= 0 {
return false, ledger.VipState{}, xerr.New(xerr.InvalidArgument, "vip trial unequip command is incomplete")
}
if len(command.RequestID) > 128 {
return false, ledger.VipState{}, xerr.New(xerr.InvalidArgument, "request_id is too long")
}
if s.repository == nil {
return false, ledger.VipState{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.UnequipVipTrialCard(ctx, command)
}
// ListAdminVipLevels 返回后台 VIP program 与动态等级的同一数据库快照。
func (s *Service) ListAdminVipLevels(ctx context.Context) (ledger.VipProgramConfig, []ledger.VipLevel, error) {
if s.repository == nil {
return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
return s.repository.ListAdminVipLevels(ctx)
}
// UpdateAdminVipLevels 保存后台 VIP 等级配置;完整校验下沉到 repository保证和购买事务一致。
func (s *Service) UpdateAdminVipLevels(ctx context.Context, levels []ledger.AdminVipLevelCommand, operatorUserID int64) (ledger.VipProgramConfig, []ledger.VipLevel, error) {
if operatorUserID <= 0 {
return ledger.VipProgramConfig{}, nil, xerr.New(xerr.InvalidArgument, "operator_user_id is required")
}
if s.repository == nil {
return ledger.VipProgramConfig{}, nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
normalized := make([]ledger.AdminVipLevelCommand, 0, len(levels))
for _, level := range levels {
level.Name = strings.TrimSpace(level.Name)
level.Status = strings.ToLower(strings.TrimSpace(level.Status))
normalized = append(normalized, level)
}
ctx = appcode.WithContext(ctx, appcode.FromContext(ctx))
return s.repository.UpdateAdminVipLevels(ctx, normalized, operatorUserID)
}
// UpdateAdminVipProgramConfig 保存 App 级状态机配置;版本递增与字段白名单由 repository 在锁内处理。
func (s *Service) UpdateAdminVipProgramConfig(ctx context.Context, command ledger.AdminVipProgramConfigCommand) (ledger.VipProgramConfig, error) {
if command.OperatorUserID <= 0 {
return ledger.VipProgramConfig{}, xerr.New(xerr.InvalidArgument, "operator_user_id is required")
}
if s.repository == nil {
return ledger.VipProgramConfig{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.Config.AppCode = appcode.Normalize(command.Config.AppCode)
ctx = appcode.WithContext(ctx, command.Config.AppCode)
return s.repository.UpdateAdminVipProgramConfig(ctx, command)
}
// UpdateVipUserSettings 保存用户可关闭的 VIP 通知偏好。用户当前没有 VIP 也允许保存,
// 但偏好不会授予权益;实际执行仍由 CheckVipBenefit 同时校验 effective benefit。
func (s *Service) UpdateVipUserSettings(ctx context.Context, command ledger.UpdateVipUserSettingsCommand) (ledger.VipUserSettings, error) {
if command.UserID <= 0 || (command.RoomEntryNoticeEnabled == nil && command.OnlineGlobalNoticeEnabled == nil) {
return ledger.VipUserSettings{}, xerr.New(xerr.InvalidArgument, "vip user settings update is incomplete")
}
if s.repository == nil {
return ledger.VipUserSettings{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
command.AppCode = appcode.Normalize(command.AppCode)
ctx = appcode.WithContext(ctx, command.AppCode)
return s.repository.UpdateVipUserSettings(ctx, command)
}