取消vip门槛
This commit is contained in:
parent
0a072894ec
commit
7a2d292c36
@ -1,12 +0,0 @@
|
||||
brokerClusterName = hyapp-local
|
||||
brokerName = broker-a
|
||||
brokerId = 0
|
||||
deleteWhen = 04
|
||||
fileReservedTime = 48
|
||||
brokerRole = ASYNC_MASTER
|
||||
flushDiskType = ASYNC_FLUSH
|
||||
autoCreateTopicEnable = true
|
||||
# Local RocketMQ clients run in two places: Docker containers and host go run
|
||||
# processes. Advertising the Docker host address keeps both paths reachable.
|
||||
brokerIP1 = host.docker.internal
|
||||
listenPort = 10911
|
||||
@ -1,92 +0,0 @@
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"424242","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"12345678","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"880585934","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"880585935","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"881586014","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"881586015","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"882586332","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"882586333","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990000001","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990000002","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686001","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686002","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686003","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686004","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686005","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686006","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686007","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686008","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686009","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990686010","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687001","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687002","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687003","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687004","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687005","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687006","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687007","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687008","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687009","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990687010","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688001","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688002","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688003","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688004","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688005","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688006","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688007","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688008","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688009","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990688010","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900000971589","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900000971590","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900000971617","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900000971618","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900000971718","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900000971719","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900503651195","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900503651196","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900503651197","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"900503651198","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"910004935471","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"910004935472","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"910004935473","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"910004935474","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"910004936181","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"910004936182","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"910004936183","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"910004936184","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"930000000001","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"930000000002","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"975824912277","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"975824912278","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"975825864464","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"975825864465","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"975827047904","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"975827047905","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"975828064123","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"975828064124","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990778061358","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"990778062287","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"991205120003","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"1778070991803","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"1778070991804","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"1778071196761","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"1778071196762","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865153","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865154","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865155","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865156","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865157","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865158","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865159","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865160","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865161","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"7049835865162","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"318705991371722752","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"318707093894860800","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"318707097803952128","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"318707100643495936","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"318707103256547328","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"318707105987039232","status":"dry_run"}
|
||||
{"at_ms":1780381127156,"mode":"dry-run","to_account":"320078559995498496","status":"dry_run"}
|
||||
6
scripts/mysql/056_reset_vip_recharge_thresholds.sql
Normal file
6
scripts/mysql/056_reset_vip_recharge_thresholds.sql
Normal file
@ -0,0 +1,6 @@
|
||||
USE hyapp_wallet;
|
||||
|
||||
-- VIP 购买已改为所有等级直接按金币购买;保留字段只为兼容旧协议,线上已有累充门槛统一清零。
|
||||
UPDATE vip_levels
|
||||
SET required_recharge_coin_amount = 0
|
||||
WHERE required_recharge_coin_amount <> 0;
|
||||
@ -139,13 +139,14 @@ func (h *Handler) UpdateLevels(c *gin.Context) {
|
||||
input := make([]*walletv1.AdminVipLevelInput, 0, len(req.Levels))
|
||||
for _, level := range req.Levels {
|
||||
input = append(input, &walletv1.AdminVipLevelInput{
|
||||
Level: level.Level,
|
||||
Name: strings.TrimSpace(level.Name),
|
||||
Status: strings.TrimSpace(level.Status),
|
||||
PriceCoin: level.PriceCoin,
|
||||
DurationMs: level.DurationMS,
|
||||
RewardResourceGroupId: level.RewardResourceGroupID,
|
||||
RequiredRechargeCoinAmount: level.RequiredRechargeCoinAmount,
|
||||
Level: level.Level,
|
||||
Name: strings.TrimSpace(level.Name),
|
||||
Status: strings.TrimSpace(level.Status),
|
||||
PriceCoin: level.PriceCoin,
|
||||
DurationMs: level.DurationMS,
|
||||
RewardResourceGroupId: level.RewardResourceGroupID,
|
||||
// VIP 累充门槛已下线;后台旧请求即使携带旧字段,也统一写 0,避免旧页面或缓存把门槛重新保存回来。
|
||||
RequiredRechargeCoinAmount: 0,
|
||||
SortOrder: level.SortOrder,
|
||||
})
|
||||
}
|
||||
@ -170,16 +171,17 @@ func vipLevelsFromProto(items []*walletv1.VipLevel) []vipLevelDTO {
|
||||
continue
|
||||
}
|
||||
levels = append(levels, vipLevelDTO{
|
||||
Level: item.GetLevel(),
|
||||
Name: item.GetName(),
|
||||
Status: item.GetStatus(),
|
||||
PriceCoin: item.GetPriceCoin(),
|
||||
DurationMS: item.GetDurationMs(),
|
||||
RewardResourceGroupID: item.GetRewardResourceGroupId(),
|
||||
SortOrder: item.GetSortOrder(),
|
||||
RechargeGateRequired: item.GetRechargeGateRequired(),
|
||||
RequiredRechargeCoinAmount: item.GetRequiredRechargeCoinAmount(),
|
||||
UserRechargeCoinAmount: item.GetUserRechargeCoinAmount(),
|
||||
Level: item.GetLevel(),
|
||||
Name: item.GetName(),
|
||||
Status: item.GetStatus(),
|
||||
PriceCoin: item.GetPriceCoin(),
|
||||
DurationMS: item.GetDurationMs(),
|
||||
RewardResourceGroupID: item.GetRewardResourceGroupId(),
|
||||
SortOrder: item.GetSortOrder(),
|
||||
// 响应仍保留兼容字段,但后台配置页不再展示或依赖累充资格,统一返回无门槛。
|
||||
RechargeGateRequired: false,
|
||||
RequiredRechargeCoinAmount: 0,
|
||||
UserRechargeCoinAmount: 0,
|
||||
PurchaseLockedReason: item.GetPurchaseLockedReason(),
|
||||
CreatedAtMS: item.GetCreatedAtMs(),
|
||||
UpdatedAtMS: item.GetUpdatedAtMs(),
|
||||
|
||||
@ -356,30 +356,30 @@ func TestGrantManagerResourceRejectsCrossCountryTarget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGrantManagerVIPLimitsToVipOneToThree(t *testing.T) {
|
||||
func TestGrantManagerVIPRejectsLevelAboveManagerLimit(t *testing.T) {
|
||||
walletClient := &fakeWalletClient{}
|
||||
router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, &fakeUserProfileClient{})
|
||||
body := []byte(`{"command_id":"mgr-vip-4","target_user_id":"900001","level":4}`)
|
||||
body := []byte(`{"command_id":"mgr-vip-6","target_user_id":"900001","level":6}`)
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/vip-grants", bytes.NewReader(body))
|
||||
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
|
||||
request.Header.Set("X-Request-ID", "req-manager-vip4")
|
||||
request.Header.Set("X-Request-ID", "req-manager-vip6")
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(recorder, request)
|
||||
|
||||
assertEnvelope(t, recorder, http.StatusBadRequest, httpkit.CodeInvalidArgument, "req-manager-vip4")
|
||||
assertEnvelope(t, recorder, http.StatusBadRequest, httpkit.CodeInvalidArgument, "req-manager-vip6")
|
||||
if walletClient.lastGrantVip != nil {
|
||||
t.Fatalf("VIP4 must not reach wallet-service: %+v", walletClient.lastGrantVip)
|
||||
t.Fatalf("VIP6 must not reach wallet-service: %+v", walletClient.lastGrantVip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGrantManagerVIPUsesManagerCenterGrantSource(t *testing.T) {
|
||||
walletClient := &fakeWalletClient{}
|
||||
router := newManagerCenterTestRouter(walletClient, &fakeUserHostClient{}, &fakeUserProfileClient{})
|
||||
body := []byte(`{"command_id":"mgr-vip-3","target_user_id":"900001","level":3}`)
|
||||
body := []byte(`{"command_id":"mgr-vip-5","target_user_id":"900001","level":5}`)
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/v1/manager-center/vip-grants", bytes.NewReader(body))
|
||||
request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
|
||||
request.Header.Set("X-Request-ID", "req-manager-vip3")
|
||||
request.Header.Set("X-Request-ID", "req-manager-vip5")
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(recorder, request)
|
||||
@ -387,7 +387,7 @@ func TestGrantManagerVIPUsesManagerCenterGrantSource(t *testing.T) {
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if req := walletClient.lastGrantVip; req == nil || req.GetCommandId() != "mgr-vip-3" || req.GetTargetUserId() != 900001 || req.GetLevel() != 3 || req.GetGrantSource() != "manager_center" || req.GetOperatorUserId() != 42 {
|
||||
if req := walletClient.lastGrantVip; req == nil || req.GetCommandId() != "mgr-vip-5" || req.GetTargetUserId() != 900001 || req.GetLevel() != 5 || req.GetGrantSource() != "manager_center" || req.GetOperatorUserId() != 42 {
|
||||
t.Fatalf("wallet GrantVip request mismatch: %+v", req)
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ const managerBlockUserCapability = "manager_block_user"
|
||||
const managerTransferUserCountryCapability = "manager_transfer_user_country"
|
||||
const managerGrantDayMS int64 = 24 * 60 * 60 * 1000
|
||||
const managerGrantDefaultDurationMS int64 = 7 * managerGrantDayMS
|
||||
const managerGrantMaxVIPLevel int32 = 5
|
||||
|
||||
type managerGrantResourceData struct {
|
||||
ResourceID string `json:"resource_id"`
|
||||
@ -397,7 +398,8 @@ func (h *Handler) grantManagerVIP(writer http.ResponseWriter, request *http.Requ
|
||||
commandID = strings.TrimSpace(body.CommandIDAlt)
|
||||
}
|
||||
targetUserID, targetOK := rawInt64(body.TargetUserID, body.TargetUserIDAlt)
|
||||
if commandID == "" || !targetOK || targetUserID <= 0 || body.Level < 1 || body.Level > 3 {
|
||||
// 经理中心只放开 VIP1-5 的运营赠送入口;钱包仍按 vip_levels.status 做最终配置校验,避免 H5 放开后绕过等级启停。
|
||||
if commandID == "" || !targetOK || targetUserID <= 0 || body.Level < 1 || body.Level > managerGrantMaxVIPLevel {
|
||||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||||
return
|
||||
}
|
||||
|
||||
@ -1425,7 +1425,7 @@ CREATE TABLE IF NOT EXISTS vip_levels (
|
||||
price_coin BIGINT NOT NULL COMMENT 'VIP 价格金币数',
|
||||
duration_ms BIGINT NOT NULL COMMENT '持续时长,毫秒',
|
||||
reward_resource_group_id BIGINT NOT NULL DEFAULT 0 COMMENT '奖励资源分组 ID',
|
||||
required_recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '购买所需累计充值金币,1-5 级固定为 0,6-10 级必须配置',
|
||||
required_recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT '兼容旧 VIP 累充门槛字段,当前不参与购买资格',
|
||||
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重,数值越小越靠前',
|
||||
created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms',
|
||||
updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms',
|
||||
@ -1435,7 +1435,7 @@ CREATE TABLE IF NOT EXISTS vip_levels (
|
||||
|
||||
SET @ddl := IF(
|
||||
(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'vip_levels' AND COLUMN_NAME = 'required_recharge_coin_amount') = 0,
|
||||
'ALTER TABLE vip_levels ADD COLUMN required_recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT ''购买所需累计充值金币,1-5 级固定为 0,6-10 级必须配置'' AFTER reward_resource_group_id',
|
||||
'ALTER TABLE vip_levels ADD COLUMN required_recharge_coin_amount BIGINT NOT NULL DEFAULT 0 COMMENT ''兼容旧 VIP 累充门槛字段,当前不参与购买资格'' AFTER reward_resource_group_id',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
|
||||
@ -5480,8 +5480,8 @@ func TestGrantVipUsesUnifiedActivationAndNoticeOutbox(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestVipLevelSixRequiresRechargeThreshold 验证 6-10 级 VIP 必须先达到累计充值门槛。
|
||||
func TestVipLevelSixRequiresRechargeThreshold(t *testing.T) {
|
||||
// TestVipLevelSixIgnoresLegacyRechargeThreshold 验证 VIP6 即使存在旧累充字段,也能按金币价格直接购买。
|
||||
func TestVipLevelSixIgnoresLegacyRechargeThreshold(t *testing.T) {
|
||||
repository := mysqltest.NewRepository(t)
|
||||
repository.SetBalance(51002, 50000)
|
||||
svc := walletservice.New(repository)
|
||||
@ -5517,32 +5517,15 @@ func TestVipLevelSixRequiresRechargeThreshold(t *testing.T) {
|
||||
if _, err := svc.UpdateAdminVipLevels(context.Background(), commands, 9001); err != nil {
|
||||
t.Fatalf("UpdateAdminVipLevels failed: %v", err)
|
||||
}
|
||||
repository.SetVIPLevelRechargeThreshold(6, 5000)
|
||||
|
||||
_, packages, err := svc.ListVipPackages(context.Background(), 51002)
|
||||
if err != nil {
|
||||
t.Fatalf("ListVipPackages failed: %v", err)
|
||||
}
|
||||
level6 := vipLevelByNumber(packages, 6)
|
||||
if level6 == nil || level6.CanPurchase || level6.PurchaseLockedReason != "recharge_required" || level6.RequiredRechargeCoinAmount != 5000 {
|
||||
t.Fatalf("vip level 6 should be locked by recharge gate: %+v", level6)
|
||||
}
|
||||
_, err = svc.PurchaseVip(context.Background(), ledger.PurchaseVipCommand{
|
||||
CommandID: "cmd-vip6-locked",
|
||||
UserID: 51002,
|
||||
Level: 6,
|
||||
})
|
||||
if !xerr.IsCode(err, xerr.VIPRechargeRequired) {
|
||||
t.Fatalf("expected VIP_RECHARGE_REQUIRED, got %v", err)
|
||||
}
|
||||
|
||||
repository.SetRechargeStats(51002, 5000)
|
||||
_, packages, err = svc.ListVipPackages(context.Background(), 51002)
|
||||
if err != nil {
|
||||
t.Fatalf("ListVipPackages after recharge failed: %v", err)
|
||||
}
|
||||
level6 = vipLevelByNumber(packages, 6)
|
||||
if level6 == nil || !level6.CanPurchase || level6.UserRechargeCoinAmount != 5000 {
|
||||
t.Fatalf("vip level 6 should be purchasable after recharge: %+v", level6)
|
||||
if level6 == nil || !level6.CanPurchase || level6.PurchaseLockedReason != "" || level6.RechargeGateRequired || level6.RequiredRechargeCoinAmount != 0 || level6.UserRechargeCoinAmount != 0 {
|
||||
t.Fatalf("vip level 6 should ignore legacy recharge gate: %+v", level6)
|
||||
}
|
||||
receipt, err := svc.PurchaseVip(context.Background(), ledger.PurchaseVipCommand{
|
||||
CommandID: "cmd-vip6",
|
||||
|
||||
@ -89,9 +89,8 @@ func normalizeAdminVipLevelCommands(levels []ledger.AdminVipLevelCommand) ([]led
|
||||
if _, exists := byLevel[level.Level]; exists {
|
||||
return nil, xerr.New(xerr.InvalidArgument, "vip level is duplicated")
|
||||
}
|
||||
if level.Level <= 5 {
|
||||
level.RequiredRechargeCoinAmount = 0
|
||||
}
|
||||
// 累充门槛字段仅保留数据库和 proto 兼容性;后台保存任意等级时都归零,防止旧客户端把历史门槛重新写入生效数据。
|
||||
level.RequiredRechargeCoinAmount = 0
|
||||
if err := validateAdminVipLevelCommand(level); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -129,9 +128,6 @@ func validateAdminVipLevelCommand(level ledger.AdminVipLevelCommand) error {
|
||||
if level.RewardResourceGroupID <= 0 {
|
||||
return xerr.New(xerr.InvalidArgument, "active vip level reward_resource_group_id is required")
|
||||
}
|
||||
if level.Level >= 6 && level.RequiredRechargeCoinAmount <= 0 {
|
||||
return xerr.New(xerr.InvalidArgument, "vip level 6-10 requires recharge threshold")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -57,15 +57,6 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip
|
||||
return ledger.PurchaseVipReceipt{}, xerr.New(xerr.VIPDowngradeNotAllowed, "vip downgrade is not allowed")
|
||||
}
|
||||
}
|
||||
if level.RechargeGateRequired {
|
||||
totalRechargeCoin, err := r.userRechargeCoinAmount(ctx, tx, command.UserID)
|
||||
if err != nil {
|
||||
return ledger.PurchaseVipReceipt{}, err
|
||||
}
|
||||
if totalRechargeCoin < level.RequiredRechargeCoinAmount {
|
||||
return ledger.PurchaseVipReceipt{}, xerr.New(xerr.VIPRechargeRequired, "vip recharge requirement is not met")
|
||||
}
|
||||
}
|
||||
newExpiresAt := baseExpiresAt + level.DurationMS
|
||||
account, err := r.lockAccount(ctx, tx, command.UserID, ledger.AssetCoin, false, nowMs)
|
||||
if err != nil {
|
||||
@ -79,17 +70,18 @@ func (r *Repository) PurchaseVip(ctx context.Context, command ledger.PurchaseVip
|
||||
orderID := "vip_order_" + stableHash(command.AppCode+"|"+command.CommandID)
|
||||
coinBalanceAfter := account.AvailableAmount - level.PriceCoin
|
||||
metadata := map[string]any{
|
||||
"app_code": command.AppCode,
|
||||
"order_id": orderID,
|
||||
"user_id": command.UserID,
|
||||
"target_level": command.Level,
|
||||
"previous_level": previousLevel,
|
||||
"price_coin": level.PriceCoin,
|
||||
"expires_at_ms": newExpiresAt,
|
||||
"balance_after": coinBalanceAfter,
|
||||
"reward_group_id": level.RewardResourceGroupID,
|
||||
"duration_ms": level.DurationMS,
|
||||
"required_recharge_coin_amount": level.RequiredRechargeCoinAmount,
|
||||
"app_code": command.AppCode,
|
||||
"order_id": orderID,
|
||||
"user_id": command.UserID,
|
||||
"target_level": command.Level,
|
||||
"previous_level": previousLevel,
|
||||
"price_coin": level.PriceCoin,
|
||||
"expires_at_ms": newExpiresAt,
|
||||
"balance_after": coinBalanceAfter,
|
||||
"reward_group_id": level.RewardResourceGroupID,
|
||||
"duration_ms": level.DurationMS,
|
||||
// 兼容旧账务元数据字段;业务已不再按累计充值限制 VIP 购买,因此固定记录 0。
|
||||
"required_recharge_coin_amount": int64(0),
|
||||
}
|
||||
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeVIPPurchase, requestHash, orderID, metadata, nowMs); err != nil {
|
||||
return ledger.PurchaseVipReceipt{}, err
|
||||
|
||||
@ -33,11 +33,7 @@ func (r *Repository) ListVipPackages(ctx context.Context, userID int64) (ledger.
|
||||
if err != nil {
|
||||
return ledger.UserVip{}, nil, err
|
||||
}
|
||||
totalRechargeCoin, err := r.userRechargeCoinAmount(ctx, r.db, userID)
|
||||
if err != nil {
|
||||
return ledger.UserVip{}, nil, err
|
||||
}
|
||||
levels, err := r.listVipLevels(ctx, nowMs, current.Level, totalRechargeCoin)
|
||||
levels, err := r.listVipLevels(ctx, nowMs, current.Level)
|
||||
return current, levels, err
|
||||
}
|
||||
|
||||
@ -76,7 +72,7 @@ func (r *Repository) ListAdminVipLevels(ctx context.Context) ([]ledger.VipLevel,
|
||||
return levels, rows.Err()
|
||||
}
|
||||
|
||||
func (r *Repository) listVipLevels(ctx context.Context, nowMs int64, currentLevel int32, totalRechargeCoin int64) ([]ledger.VipLevel, error) {
|
||||
func (r *Repository) listVipLevels(ctx context.Context, nowMs int64, currentLevel int32) ([]ledger.VipLevel, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT level, name, status, price_coin, duration_ms, reward_resource_group_id,
|
||||
required_recharge_coin_amount, sort_order, created_at_ms, updated_at_ms
|
||||
@ -96,16 +92,14 @@ func (r *Repository) listVipLevels(ctx context.Context, nowMs int64, currentLeve
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
level.UserRechargeCoinAmount = totalRechargeCoin
|
||||
level.RechargeGateRequired = level.Level >= 6
|
||||
// VIP 累充门槛已下线,旧字段只作为兼容输出保留;购买资格只看当前 VIP 等级和配置启停。
|
||||
level.UserRechargeCoinAmount = 0
|
||||
level.RechargeGateRequired = false
|
||||
level.RequiredRechargeCoinAmount = 0
|
||||
level.CanPurchase = currentLevel == 0 || level.Level >= currentLevel
|
||||
if !level.CanPurchase {
|
||||
level.PurchaseLockedReason = "current_vip_higher"
|
||||
}
|
||||
if level.CanPurchase && level.RechargeGateRequired && totalRechargeCoin < level.RequiredRechargeCoinAmount {
|
||||
level.CanPurchase = false
|
||||
level.PurchaseLockedReason = "recharge_required"
|
||||
}
|
||||
level.RewardItems, err = r.vipRewardItems(ctx, level.RewardResourceGroupID, nowMs+level.DurationMS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -129,26 +123,12 @@ func (r *Repository) scanVipLevel(scanner scanTarget) (ledger.VipLevel, error) {
|
||||
&level.CreatedAtMS,
|
||||
&level.UpdatedAtMS,
|
||||
)
|
||||
level.RechargeGateRequired = level.Level >= 6
|
||||
// 线上历史行可能还保存 VIP6+ 累充门槛;扫描后立即归零,保证后台、App 列表和购买事务读到同一个“无门槛”事实。
|
||||
level.RechargeGateRequired = false
|
||||
level.RequiredRechargeCoinAmount = 0
|
||||
return level, err
|
||||
}
|
||||
|
||||
func (r *Repository) userRechargeCoinAmount(ctx context.Context, querier interface {
|
||||
QueryRowContext(context.Context, string, ...any) *sql.Row
|
||||
}, userID int64) (int64, error) {
|
||||
var total int64
|
||||
err := querier.QueryRowContext(ctx, `
|
||||
SELECT total_coin_amount
|
||||
FROM wallet_user_recharge_stats
|
||||
WHERE app_code = ? AND user_id = ?`,
|
||||
appcode.FromContext(ctx), userID,
|
||||
).Scan(&total)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return 0, nil
|
||||
}
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (r *Repository) ensureUserVipForUpdate(ctx context.Context, tx *sql.Tx, userID int64, nowMs int64) (ledger.UserVip, error) {
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
INSERT IGNORE INTO user_vip_memberships (
|
||||
|
||||
@ -599,7 +599,7 @@ func (r *Repository) SetRechargePolicy(regionID int64, policyVersion string, coi
|
||||
}
|
||||
}
|
||||
|
||||
// SetRechargeStats prepares cumulative recharge stats for VIP gate tests.
|
||||
// SetRechargeStats prepares cumulative recharge stats for recharge-related wallet tests.
|
||||
func (r *Repository) SetRechargeStats(userID int64, totalCoinAmount int64) {
|
||||
r.t.Helper()
|
||||
|
||||
@ -618,6 +618,21 @@ func (r *Repository) SetRechargeStats(userID int64, totalCoinAmount int64) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetVIPLevelRechargeThreshold writes a legacy VIP threshold directly, so tests can verify old rows no longer affect purchase eligibility.
|
||||
func (r *Repository) SetVIPLevelRechargeThreshold(level int32, totalCoinAmount int64) {
|
||||
r.t.Helper()
|
||||
|
||||
nowMs := time.Now().UnixMilli()
|
||||
if _, err := r.schema.DB.ExecContext(context.Background(), `
|
||||
UPDATE vip_levels
|
||||
SET required_recharge_coin_amount = ?, updated_at_ms = ?
|
||||
WHERE level = ?`,
|
||||
totalCoinAmount, nowMs, level,
|
||||
); err != nil {
|
||||
r.t.Fatalf("set vip level recharge threshold failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetHostSalaryPolicy seeds the wallet runtime policy snapshot used by salary settlement tests.
|
||||
func (r *Repository) SetHostSalaryPolicy(policy ledger.HostSalaryPolicy) {
|
||||
r.t.Helper()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user