增加首冲

This commit is contained in:
hy001 2026-05-28 15:07:59 +08:00
parent e738fbc44a
commit b53313f62d
12 changed files with 346 additions and 92 deletions

View File

@ -4,11 +4,12 @@ import "time"
// FirstRechargeRewardConfig stores first recharge reward config per system. // FirstRechargeRewardConfig stores first recharge reward config per system.
type FirstRechargeRewardConfig struct { type FirstRechargeRewardConfig struct {
ID int64 `gorm:"column:id;primaryKey"` ID int64 `gorm:"column:id;primaryKey"`
SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_first_recharge_reward_sys_origin"` SysOrigin string `gorm:"column:sys_origin;size:32;uniqueIndex:uk_first_recharge_reward_sys_origin"`
Enabled bool `gorm:"column:enabled;index:idx_first_recharge_reward_enabled"` Enabled bool `gorm:"column:enabled;index:idx_first_recharge_reward_enabled"`
CreateTime time.Time `gorm:"column:create_time"` MinAppVersion string `gorm:"column:min_app_version;size:32"`
UpdateTime time.Time `gorm:"column:update_time"` CreateTime time.Time `gorm:"column:create_time"`
UpdateTime time.Time `gorm:"column:update_time"`
} }
func (FirstRechargeRewardConfig) TableName() string { return "first_recharge_reward_config" } func (FirstRechargeRewardConfig) TableName() string { return "first_recharge_reward_config" }
@ -19,6 +20,7 @@ type FirstRechargeRewardLevel struct {
ConfigID int64 `gorm:"column:config_id;uniqueIndex:uk_first_recharge_reward_level,priority:1;uniqueIndex:uk_first_recharge_reward_amount,priority:1;index:idx_first_recharge_reward_level_config"` ConfigID int64 `gorm:"column:config_id;uniqueIndex:uk_first_recharge_reward_level,priority:1;uniqueIndex:uk_first_recharge_reward_amount,priority:1;index:idx_first_recharge_reward_level_config"`
Level int `gorm:"column:level;uniqueIndex:uk_first_recharge_reward_level,priority:2"` Level int `gorm:"column:level;uniqueIndex:uk_first_recharge_reward_level,priority:2"`
RechargeAmountCents int64 `gorm:"column:recharge_amount_cents;uniqueIndex:uk_first_recharge_reward_amount,priority:2"` RechargeAmountCents int64 `gorm:"column:recharge_amount_cents;uniqueIndex:uk_first_recharge_reward_amount,priority:2"`
GoogleProductID string `gorm:"column:google_product_id;size:128;index:idx_first_recharge_reward_level_google_product"`
RewardGroupID int64 `gorm:"column:reward_group_id"` RewardGroupID int64 `gorm:"column:reward_group_id"`
RewardGroupName string `gorm:"column:reward_group_name;size:255"` RewardGroupName string `gorm:"column:reward_group_name;size:255"`
Enabled bool `gorm:"column:enabled"` Enabled bool `gorm:"column:enabled"`
@ -46,6 +48,7 @@ type FirstRechargeRewardGrantRecord struct {
Level int `gorm:"column:level"` Level int `gorm:"column:level"`
PayPlatform string `gorm:"column:pay_platform;size:64"` PayPlatform string `gorm:"column:pay_platform;size:64"`
PaymentMethod string `gorm:"column:payment_method;size:32"` PaymentMethod string `gorm:"column:payment_method;size:32"`
GoogleProductID string `gorm:"column:google_product_id;size:128;index:idx_first_recharge_reward_grant_google_product"`
SourceOrderID string `gorm:"column:source_order_id;size:128;index:idx_first_recharge_reward_order"` SourceOrderID string `gorm:"column:source_order_id;size:128;index:idx_first_recharge_reward_order"`
RewardGroupID int64 `gorm:"column:reward_group_id"` RewardGroupID int64 `gorm:"column:reward_group_id"`
RewardGroupName string `gorm:"column:reward_group_name;size:255"` RewardGroupName string `gorm:"column:reward_group_name;size:255"`

View File

@ -2,6 +2,7 @@ package router
import ( import (
"net/http" "net/http"
"strings"
"chatapp3-golang/internal/service/firstrechargereward" "chatapp3-golang/internal/service/firstrechargereward"
@ -60,7 +61,7 @@ func registerFirstRechargeRewardRoutes(engine *gin.Engine, javaClient authGatewa
func registerFirstRechargeRewardAppGroup(group *gin.RouterGroup, javaClient authGateway, service *firstrechargereward.Service) { func registerFirstRechargeRewardAppGroup(group *gin.RouterGroup, javaClient authGateway, service *firstrechargereward.Service) {
group.Use(authMiddleware(javaClient)) group.Use(authMiddleware(javaClient))
group.GET("/home", func(c *gin.Context) { group.GET("/home", func(c *gin.Context) {
resp, err := service.GetHome(c.Request.Context(), mustAuthUser(c)) resp, err := service.GetHome(c.Request.Context(), mustAuthUser(c), firstRechargeRewardRequestVersion(c))
if err != nil { if err != nil {
writeError(c, err) writeError(c, err)
return return
@ -68,3 +69,33 @@ func registerFirstRechargeRewardAppGroup(group *gin.RouterGroup, javaClient auth
writeOK(c, resp) writeOK(c, resp)
}) })
} }
func firstRechargeRewardRequestVersion(c *gin.Context) string {
return firstNonEmptyString(
c.Query("appVersion"),
c.Query("version"),
c.GetHeader("req-version"),
c.GetHeader("Req-Version"),
versionFromReqAppIntel(c.GetHeader("req-app-intel")),
versionFromReqAppIntel(c.GetHeader("Req-App-Intel")),
)
}
func versionFromReqAppIntel(value string) string {
for _, item := range strings.Split(value, ";") {
parts := strings.SplitN(item, "=", 2)
if len(parts) == 2 && strings.EqualFold(strings.TrimSpace(parts[0]), "version") {
return strings.TrimSpace(parts[1])
}
}
return ""
}
func firstNonEmptyString(values ...string) string {
for _, value := range values {
if text := strings.TrimSpace(value); text != "" {
return text
}
}
return ""
}

View File

@ -37,12 +37,13 @@ func (s *Service) GetConfig(ctx context.Context, sysOrigin string) (*ConfigRespo
} }
rewardItems := s.loadRewardItemsMap(ctx, levels) rewardItems := s.loadRewardItemsMap(ctx, levels)
return &ConfigResponse{ return &ConfigResponse{
Configured: true, Configured: true,
ID: configRow.ID, ID: configRow.ID,
SysOrigin: configRow.SysOrigin, SysOrigin: configRow.SysOrigin,
Enabled: configRow.Enabled, Enabled: configRow.Enabled,
UpdateTime: formatDateTime(configRow.UpdateTime), MinAppVersion: strings.TrimSpace(configRow.MinAppVersion),
LevelConfigs: buildLevelPayloads(levels, rewardItems, 0, 0, true), UpdateTime: formatDateTime(configRow.UpdateTime),
LevelConfigs: buildLevelPayloads(levels, rewardItems, 0, 0, true),
}, nil }, nil
} }
@ -56,6 +57,10 @@ func (s *Service) SaveConfig(ctx context.Context, req SaveConfigRequest) (*Confi
if req.Enabled && !hasEnabledLevel(levels) { if req.Enabled && !hasEnabledLevel(levels) {
return nil, NewAppError(http.StatusBadRequest, "invalid_level_configs", "enabled config must contain at least one enabled level") return nil, NewAppError(http.StatusBadRequest, "invalid_level_configs", "enabled config must contain at least one enabled level")
} }
minAppVersion := strings.TrimSpace(req.MinAppVersion)
if req.Enabled && minAppVersion == "" {
return nil, NewAppError(http.StatusBadRequest, "invalid_min_app_version", "minAppVersion is required when enabled")
}
var savedConfigID int64 var savedConfigID int64
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
@ -91,6 +96,7 @@ func (s *Service) SaveConfig(ctx context.Context, req SaveConfigRequest) (*Confi
configRow.SysOrigin = sysOrigin configRow.SysOrigin = sysOrigin
configRow.Enabled = req.Enabled configRow.Enabled = req.Enabled
configRow.MinAppVersion = minAppVersion
configRow.UpdateTime = now configRow.UpdateTime = now
if configRow.CreateTime.IsZero() { if configRow.CreateTime.IsZero() {
configRow.CreateTime = now configRow.CreateTime = now
@ -126,6 +132,7 @@ func (s *Service) normalizeLevelInputs(inputs []LevelInput) ([]model.FirstRechar
} }
seenLevels := make(map[int]struct{}, len(inputs)) seenLevels := make(map[int]struct{}, len(inputs))
seenAmounts := make(map[int64]struct{}, len(inputs)) seenAmounts := make(map[int64]struct{}, len(inputs))
seenGoogleProductIDs := make(map[string]struct{}, len(inputs))
rows := make([]model.FirstRechargeRewardLevel, 0, len(inputs)) rows := make([]model.FirstRechargeRewardLevel, 0, len(inputs))
for _, item := range inputs { for _, item := range inputs {
if item.Level <= 0 { if item.Level <= 0 {
@ -148,6 +155,13 @@ func (s *Service) normalizeLevelInputs(inputs []LevelInput) ([]model.FirstRechar
if _, exists := seenAmounts[amountCents]; exists { if _, exists := seenAmounts[amountCents]; exists {
return nil, NewAppError(http.StatusBadRequest, "duplicate_recharge_amount", "levelConfigs must not contain duplicate rechargeAmount") return nil, NewAppError(http.StatusBadRequest, "duplicate_recharge_amount", "levelConfigs must not contain duplicate rechargeAmount")
} }
googleProductID := normalizeGoogleProductID(item.GoogleProductID)
if googleProductID != "" {
if _, exists := seenGoogleProductIDs[googleProductID]; exists {
return nil, NewAppError(http.StatusBadRequest, "duplicate_google_product_id", "levelConfigs must not contain duplicate googleProductId")
}
seenGoogleProductIDs[googleProductID] = struct{}{}
}
seenLevels[item.Level] = struct{}{} seenLevels[item.Level] = struct{}{}
seenAmounts[amountCents] = struct{}{} seenAmounts[amountCents] = struct{}{}
id, err := utils.NextID() id, err := utils.NextID()
@ -158,6 +172,7 @@ func (s *Service) normalizeLevelInputs(inputs []LevelInput) ([]model.FirstRechar
ID: id, ID: id,
Level: item.Level, Level: item.Level,
RechargeAmountCents: amountCents, RechargeAmountCents: amountCents,
GoogleProductID: googleProductID,
RewardGroupID: rewardGroupID, RewardGroupID: rewardGroupID,
RewardGroupName: strings.TrimSpace(item.RewardGroupName), RewardGroupName: strings.TrimSpace(item.RewardGroupName),
Enabled: item.Enabled, Enabled: item.Enabled,

View File

@ -41,9 +41,6 @@ func (s *Service) ProcessRechargeEvent(ctx context.Context, event RechargeEvent)
} }
return nil, err return nil, err
} }
if !isFirstRechargeRewardAppVersionSupported(normalized.AppVersion) {
return &ProcessRechargeResponse{Processed: false, Reason: "app_version_not_supported"}, nil
}
if !isAcceptedPaymentMethod(normalized.PaymentMethod, normalized.PayPlatform) { if !isAcceptedPaymentMethod(normalized.PaymentMethod, normalized.PayPlatform) {
return &ProcessRechargeResponse{Processed: false, Reason: "unsupported_payment_method"}, nil return &ProcessRechargeResponse{Processed: false, Reason: "unsupported_payment_method"}, nil
} }
@ -61,9 +58,12 @@ func (s *Service) ProcessRechargeEvent(ctx context.Context, event RechargeEvent)
if !bundle.Config.Enabled { if !bundle.Config.Enabled {
return &ProcessRechargeResponse{Processed: false, Reason: "config_disabled"}, nil return &ProcessRechargeResponse{Processed: false, Reason: "config_disabled"}, nil
} }
matched, ok := pickMatchedLevel(bundle.Levels, normalized.AmountCents) if !isFirstRechargeRewardAppVersionSupported(normalized.AppVersion, bundle.Config.MinAppVersion) {
return &ProcessRechargeResponse{Processed: false, Reason: "app_version_not_supported"}, nil
}
matched, ok := pickMatchedLevel(bundle.Levels, normalized.AmountCents, normalized.GoogleProductID)
if !ok { if !ok {
return &ProcessRechargeResponse{Processed: false, Reason: "amount_not_reached"}, nil return &ProcessRechargeResponse{Processed: false, Reason: "level_not_matched"}, nil
} }
record, terminal, err := s.getOrCreateGrantRecord(ctx, normalized, bundle.Config, matched) record, terminal, err := s.getOrCreateGrantRecord(ctx, normalized, bundle.Config, matched)
@ -112,13 +112,14 @@ func (s *Service) normalizeRechargeEvent(event RechargeEvent) (normalizedRecharg
if amountCents <= 0 { if amountCents <= 0 {
amountCents = firstPositiveAmountCents(event.Amount.String(), event.AmountUSD.String(), event.USDAmount.String()) amountCents = firstPositiveAmountCents(event.Amount.String(), event.AmountUSD.String(), event.USDAmount.String())
} }
if amountCents <= 0 {
return normalizedRechargeEvent{}, NewAppError(http.StatusBadRequest, "invalid_amount", "amountCents or amount is required")
}
payPlatform := strings.ToUpper(strings.TrimSpace(event.PayPlatform)) payPlatform := strings.ToUpper(strings.TrimSpace(event.PayPlatform))
paymentMethod := strings.ToUpper(strings.TrimSpace(event.PaymentMethod)) paymentMethod := strings.ToUpper(strings.TrimSpace(event.PaymentMethod))
appVersion := normalizeRechargeAppVersion(event) appVersion := normalizeRechargeAppVersion(event)
googleProductID := normalizeRechargeGoogleProductID(event)
if amountCents <= 0 && googleProductID == "" {
return normalizedRechargeEvent{}, NewAppError(http.StatusBadRequest, "invalid_recharge_identity", "amountCents, amount, or googleProductId is required")
}
sourceOrderID := firstNonEmpty(event.SourceOrderID.String(), event.OrderID.String()) sourceOrderID := firstNonEmpty(event.SourceOrderID.String(), event.OrderID.String())
eventID := strings.TrimSpace(event.EventID) eventID := strings.TrimSpace(event.EventID)
if eventID == "" && sourceOrderID != "" { if eventID == "" && sourceOrderID != "" {
@ -132,16 +133,17 @@ func (s *Service) normalizeRechargeEvent(event RechargeEvent) (normalizedRecharg
occurredAt = time.Now() occurredAt = time.Now()
} }
return normalizedRechargeEvent{ return normalizedRechargeEvent{
EventID: eventID, EventID: eventID,
SysOrigin: sysOrigin, SysOrigin: sysOrigin,
UserID: userID, UserID: userID,
AmountCents: amountCents, AmountCents: amountCents,
Currency: firstNonEmpty(strings.ToUpper(strings.TrimSpace(event.Currency)), "USD"), Currency: firstNonEmpty(strings.ToUpper(strings.TrimSpace(event.Currency)), "USD"),
PayPlatform: payPlatform, PayPlatform: payPlatform,
PaymentMethod: paymentMethod, PaymentMethod: paymentMethod,
AppVersion: appVersion, AppVersion: appVersion,
SourceOrderID: sourceOrderID, GoogleProductID: googleProductID,
OccurredAt: occurredAt, SourceOrderID: sourceOrderID,
OccurredAt: occurredAt,
}, nil }, nil
} }
@ -187,6 +189,7 @@ func (s *Service) getOrCreateGrantRecord(
Level: level.Level, Level: level.Level,
PayPlatform: event.PayPlatform, PayPlatform: event.PayPlatform,
PaymentMethod: event.PaymentMethod, PaymentMethod: event.PaymentMethod,
GoogleProductID: event.GoogleProductID,
SourceOrderID: event.SourceOrderID, SourceOrderID: event.SourceOrderID,
RewardGroupID: level.RewardGroupID, RewardGroupID: level.RewardGroupID,
RewardGroupName: level.RewardGroupName, RewardGroupName: level.RewardGroupName,
@ -290,6 +293,7 @@ func (s *Service) pushRewardNotice(ctx context.Context, record *model.FirstRecha
"rewardGroupName": record.RewardGroupName, "rewardGroupName": record.RewardGroupName,
"payPlatform": record.PayPlatform, "payPlatform": record.PayPlatform,
"paymentMethod": record.PaymentMethod, "paymentMethod": record.PaymentMethod,
"googleProductId": record.GoogleProductID,
"noticeType": firstRechargeRewardNoticeType, "noticeType": firstRechargeRewardNoticeType,
} }
return s.java.SendOfficialNoticeCustomize(ctx, integration.OfficialNoticeCustomizeRequest{ return s.java.SendOfficialNoticeCustomize(ctx, integration.OfficialNoticeCustomizeRequest{
@ -372,6 +376,18 @@ func normalizeRechargeAppVersion(event RechargeEvent) string {
) )
} }
func normalizeRechargeGoogleProductID(event RechargeEvent) string {
return normalizeGoogleProductID(firstNonEmpty(
event.GoogleProductID,
event.ProductID.String(),
rechargePayloadString(event.Payload, "googleProductId", "productId", "product_id"),
))
}
func normalizeGoogleProductID(value string) string {
return strings.TrimSpace(value)
}
func rechargePayloadString(payload json.RawMessage, keys ...string) string { func rechargePayloadString(payload json.RawMessage, keys ...string) string {
if len(payload) == 0 { if len(payload) == 0 {
return "" return ""

View File

@ -20,7 +20,7 @@ const (
) )
// GetHome returns app-facing first recharge reward config and the user's grant state. // GetHome returns app-facing first recharge reward config and the user's grant state.
func (s *Service) GetHome(ctx context.Context, user AuthUser) (*HomeResponse, error) { func (s *Service) GetHome(ctx context.Context, user AuthUser, appVersion string) (*HomeResponse, error) {
sysOrigin, err := requireUser(user) sysOrigin, err := requireUser(user)
if err != nil { if err != nil {
return nil, err return nil, err
@ -52,12 +52,17 @@ func (s *Service) GetHome(ctx context.Context, user AuthUser) (*HomeResponse, er
ActivityStatus: activityStatusOngoing, ActivityStatus: activityStatusOngoing,
UserID: user.UserID, UserID: user.UserID,
SysOrigin: bundle.Config.SysOrigin, SysOrigin: bundle.Config.SysOrigin,
MinAppVersion: bundle.Config.MinAppVersion,
LevelConfigs: buildLevelPayloads(bundle.Levels, rewardItems, 0, 0, false), LevelConfigs: buildLevelPayloads(bundle.Levels, rewardItems, 0, 0, false),
HasRewardRecord: false, HasRewardRecord: false,
} }
if !bundle.Config.Enabled { if !bundle.Config.Enabled {
resp.ActivityStatus = activityStatusDisabled resp.ActivityStatus = activityStatusDisabled
} }
if bundle.Config.Enabled && !isFirstRechargeRewardAppVersionSupported(appVersion, bundle.Config.MinAppVersion) {
resp.Enabled = false
resp.ActivityStatus = activityStatusDisabled
}
record, ok, err := s.loadUserGrantRecord(ctx, bundle.Config.SysOrigin, user.UserID) record, ok, err := s.loadUserGrantRecord(ctx, bundle.Config.SysOrigin, user.UserID)
if err != nil { if err != nil {

View File

@ -12,10 +12,11 @@ import (
func configSnapshotFromModel(row model.FirstRechargeRewardConfig) configSnapshot { func configSnapshotFromModel(row model.FirstRechargeRewardConfig) configSnapshot {
return configSnapshot{ return configSnapshot{
ID: row.ID, ID: row.ID,
SysOrigin: strings.ToUpper(strings.TrimSpace(row.SysOrigin)), SysOrigin: strings.ToUpper(strings.TrimSpace(row.SysOrigin)),
Enabled: row.Enabled, Enabled: row.Enabled,
UpdateTime: row.UpdateTime, MinAppVersion: strings.TrimSpace(row.MinAppVersion),
UpdateTime: row.UpdateTime,
} }
} }
@ -24,6 +25,7 @@ func levelSnapshotFromModel(row model.FirstRechargeRewardLevel) levelSnapshot {
ID: row.ID, ID: row.ID,
Level: row.Level, Level: row.Level,
RechargeAmountCents: row.RechargeAmountCents, RechargeAmountCents: row.RechargeAmountCents,
GoogleProductID: normalizeGoogleProductID(row.GoogleProductID),
RewardGroupID: row.RewardGroupID, RewardGroupID: row.RewardGroupID,
RewardGroupName: strings.TrimSpace(row.RewardGroupName), RewardGroupName: strings.TrimSpace(row.RewardGroupName),
Enabled: row.Enabled, Enabled: row.Enabled,
@ -51,6 +53,7 @@ func buildLevelPayloads(
Level: level.Level, Level: level.Level,
RechargeAmount: formatAmountCents(level.RechargeAmountCents), RechargeAmount: formatAmountCents(level.RechargeAmountCents),
RechargeAmountCents: level.RechargeAmountCents, RechargeAmountCents: level.RechargeAmountCents,
GoogleProductID: level.GoogleProductID,
RewardGroupID: level.RewardGroupID, RewardGroupID: level.RewardGroupID,
RewardGroupName: level.RewardGroupName, RewardGroupName: level.RewardGroupName,
RewardItems: rewardItems[level.RewardGroupID], RewardItems: rewardItems[level.RewardGroupID],
@ -62,19 +65,27 @@ func buildLevelPayloads(
return payloads return payloads
} }
func pickMatchedLevel(levels []levelSnapshot, rechargeAmountCents int64) (levelSnapshot, bool) { func pickMatchedLevel(levels []levelSnapshot, rechargeAmountCents int64, googleProductID string) (levelSnapshot, bool) {
var matched levelSnapshot googleProductID = normalizeGoogleProductID(googleProductID)
ok := false if googleProductID != "" {
for _, level := range levels {
if !level.Enabled || level.RewardGroupID <= 0 || level.GoogleProductID == "" {
continue
}
if level.GoogleProductID == googleProductID {
return level, true
}
}
}
for _, level := range levels { for _, level := range levels {
if !level.Enabled || level.RechargeAmountCents <= 0 || level.RewardGroupID <= 0 { if !level.Enabled || level.RechargeAmountCents <= 0 || level.RewardGroupID <= 0 {
continue continue
} }
if rechargeAmountCents >= level.RechargeAmountCents && (!ok || level.RechargeAmountCents > matched.RechargeAmountCents) { if rechargeAmountCents == level.RechargeAmountCents {
matched = level return level, true
ok = true
} }
} }
return matched, ok return levelSnapshot{}, false
} }
func sortLevels(levels []levelSnapshot) { func sortLevels(levels []levelSnapshot) {

View File

@ -92,6 +92,7 @@ func grantRecordViewFromModel(row model.FirstRechargeRewardGrantRecord, rewardIt
Level: row.Level, Level: row.Level,
PayPlatform: row.PayPlatform, PayPlatform: row.PayPlatform,
PaymentMethod: row.PaymentMethod, PaymentMethod: row.PaymentMethod,
GoogleProductID: row.GoogleProductID,
SourceOrderID: row.SourceOrderID, SourceOrderID: row.SourceOrderID,
RewardGroupID: row.RewardGroupID, RewardGroupID: row.RewardGroupID,
RewardGroupName: row.RewardGroupName, RewardGroupName: row.RewardGroupName,

View File

@ -20,8 +20,9 @@ func TestSaveConfigReturnsRewardItems(t *testing.T) {
req := mustDecodeSaveConfigRequest(t, `{ req := mustDecodeSaveConfigRequest(t, `{
"sysOrigin": "LIKEI", "sysOrigin": "LIKEI",
"enabled": true, "enabled": true,
"minAppVersion": "1.5.0",
"levelConfigs": [ "levelConfigs": [
{"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true} {"level": 1, "rechargeAmount": "9.99", "googleProductId": "gold_999", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true}
] ]
}`) }`)
resp, err := service.SaveConfig(context.Background(), req) resp, err := service.SaveConfig(context.Background(), req)
@ -38,6 +39,9 @@ func TestSaveConfigReturnsRewardItems(t *testing.T) {
if level.RechargeAmountCents != 999 || level.RewardGroupID != 1001 { if level.RechargeAmountCents != 999 || level.RewardGroupID != 1001 {
t.Fatalf("level payload = %+v", level) t.Fatalf("level payload = %+v", level)
} }
if level.GoogleProductID != "gold_999" {
t.Fatalf("google product id = %q", level.GoogleProductID)
}
if len(level.RewardItems) != 1 || level.RewardItems[0].Name != "首冲礼包奖励" { if len(level.RewardItems) != 1 || level.RewardItems[0].Name != "首冲礼包奖励" {
t.Fatalf("reward items = %+v", level.RewardItems) t.Fatalf("reward items = %+v", level.RewardItems)
} }
@ -66,6 +70,7 @@ func TestSaveConfigReturnsVipCoverFromSourceURL(t *testing.T) {
resp, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{ resp, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{
"sysOrigin": "LIKEI", "sysOrigin": "LIKEI",
"enabled": true, "enabled": true,
"minAppVersion": "1.5.0",
"levelConfigs": [ "levelConfigs": [
{"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "2001", "rewardGroupName": "VIP礼包", "enabled": true} {"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "2001", "rewardGroupName": "VIP礼包", "enabled": true}
] ]
@ -115,6 +120,7 @@ func TestRewardItemsReturnConcretePropsType(t *testing.T) {
resp, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{ resp, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{
"sysOrigin": "LIKEI", "sysOrigin": "LIKEI",
"enabled": true, "enabled": true,
"minAppVersion": "1.5.0",
"levelConfigs": [ "levelConfigs": [
{"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "3001", "rewardGroupName": "道具礼包", "enabled": true} {"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "3001", "rewardGroupName": "道具礼包", "enabled": true}
] ]
@ -147,8 +153,9 @@ func TestProcessRechargeEventFiltersAndGrantsOnce(t *testing.T) {
_, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{ _, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{
"sysOrigin": "LIKEI", "sysOrigin": "LIKEI",
"enabled": true, "enabled": true,
"minAppVersion": "1.5.0",
"levelConfigs": [ "levelConfigs": [
{"level": 1, "rechargeAmount": "5.00", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true} {"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true}
] ]
}`)) }`))
if err != nil { if err != nil {
@ -185,7 +192,7 @@ func TestProcessRechargeEventFiltersAndGrantsOnce(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("low amount ProcessRechargeEvent() error = %v", err) t.Fatalf("low amount ProcessRechargeEvent() error = %v", err)
} }
if lowAmount.Processed || lowAmount.Reason != "amount_not_reached" { if lowAmount.Processed || lowAmount.Reason != "level_not_matched" {
t.Fatalf("low amount response = %+v", lowAmount) t.Fatalf("low amount response = %+v", lowAmount)
} }
@ -292,8 +299,9 @@ func TestProcessRechargeEventStartsCountingAtSupportedAppVersion(t *testing.T) {
_, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{ _, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{
"sysOrigin": "LIKEI", "sysOrigin": "LIKEI",
"enabled": true, "enabled": true,
"minAppVersion": "1.5.0",
"levelConfigs": [ "levelConfigs": [
{"level": 1, "rechargeAmount": "5.00", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true} {"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true}
] ]
}`)) }`))
if err != nil { if err != nil {
@ -380,13 +388,86 @@ func TestFirstRechargeRewardAppVersionSupported(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := isFirstRechargeRewardAppVersionSupported(tt.appVersion); got != tt.want { if got := isFirstRechargeRewardAppVersionSupported(tt.appVersion, "1.5.0"); got != tt.want {
t.Fatalf("isFirstRechargeRewardAppVersionSupported(%q) = %v, want %v", tt.appVersion, got, tt.want) t.Fatalf("isFirstRechargeRewardAppVersionSupported(%q) = %v, want %v", tt.appVersion, got, tt.want)
} }
}) })
} }
} }
func TestHomeRequiresConfiguredMinAppVersion(t *testing.T) {
service, gateway, _ := newTestService(t)
gateway.groups[1001] = testRewardGroup(1001, "首冲礼包")
_, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{
"sysOrigin": "LIKEI",
"enabled": true,
"minAppVersion": "2.0.0",
"levelConfigs": [
{"level": 1, "rechargeAmount": "9.99", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true}
]
}`))
if err != nil {
t.Fatalf("SaveConfig() error = %v", err)
}
oldVersion, err := service.GetHome(context.Background(), AuthUser{UserID: 123, SysOrigin: "LIKEI"}, "1.9.9")
if err != nil {
t.Fatalf("old version GetHome() error = %v", err)
}
if oldVersion.Enabled || oldVersion.ActivityStatus != activityStatusDisabled {
t.Fatalf("old version home = %+v", oldVersion)
}
supported, err := service.GetHome(context.Background(), AuthUser{UserID: 123, SysOrigin: "LIKEI"}, "2.0.0")
if err != nil {
t.Fatalf("supported GetHome() error = %v", err)
}
if !supported.Enabled || supported.ActivityStatus != activityStatusOngoing {
t.Fatalf("supported home = %+v", supported)
}
}
func TestProcessRechargeEventMatchesGoogleProductIDBeforeAmount(t *testing.T) {
service, gateway, _ := newTestService(t)
gateway.groups[1001] = testRewardGroup(1001, "首冲礼包")
gateway.groups[1002] = testRewardGroup(1002, "Google 首冲礼包")
_, err := service.SaveConfig(context.Background(), mustDecodeSaveConfigRequest(t, `{
"sysOrigin": "LIKEI",
"enabled": true,
"minAppVersion": "1.5.0",
"levelConfigs": [
{"level": 1, "rechargeAmount": "9.99", "googleProductId": "gold_999", "rewardGroupId": "1001", "rewardGroupName": "首冲礼包", "enabled": true},
{"level": 2, "rechargeAmount": "19.99", "googleProductId": "first_recharge_google_1999", "rewardGroupId": "1002", "rewardGroupName": "Google 首冲礼包", "enabled": true}
]
}`))
if err != nil {
t.Fatalf("SaveConfig() error = %v", err)
}
granted, err := service.ProcessRechargeEvent(context.Background(), mustDecodeRechargeEvent(t, `{
"eventId": "RECHARGE_SUCCESS:GOOGLE:PRODUCT_ID",
"sysOrigin": "LIKEI",
"userId": "123",
"amountCents": "999",
"payPlatform": "GOOGLE",
"paymentMethod": "GOOGLE",
"googleProductId": "first_recharge_google_1999",
"appVersion": "1.5.0",
"sourceOrderId": "PRODUCT_ID"
}`))
if err != nil {
t.Fatalf("product id ProcessRechargeEvent() error = %v", err)
}
if !granted.Processed || granted.Status != statusSuccess || granted.Level != 2 {
t.Fatalf("product id response = %+v", granted)
}
if len(gateway.rewardRequests) != 1 || gateway.rewardRequests[0].SourceGroupID != 1002 {
t.Fatalf("reward requests = %+v", gateway.rewardRequests)
}
}
func newTestService(t *testing.T) (*Service, *fakeGateway, *gorm.DB) { func newTestService(t *testing.T) (*Service, *fakeGateway, *gorm.DB) {
t.Helper() t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})

View File

@ -65,6 +65,7 @@ type LevelPayload struct {
Level int `json:"level"` Level int `json:"level"`
RechargeAmount string `json:"rechargeAmount"` RechargeAmount string `json:"rechargeAmount"`
RechargeAmountCents int64 `json:"rechargeAmountCents"` RechargeAmountCents int64 `json:"rechargeAmountCents"`
GoogleProductID string `json:"googleProductId,omitempty"`
RewardGroupID int64 `json:"rewardGroupId,string"` RewardGroupID int64 `json:"rewardGroupId,string"`
RewardGroupName string `json:"rewardGroupName,omitempty"` RewardGroupName string `json:"rewardGroupName,omitempty"`
RewardItems []RewardItem `json:"rewardItems"` RewardItems []RewardItem `json:"rewardItems"`
@ -78,25 +79,28 @@ type LevelInput struct {
Level int `json:"level"` Level int `json:"level"`
RechargeAmount flexibleAmount `json:"rechargeAmount"` RechargeAmount flexibleAmount `json:"rechargeAmount"`
RechargeAmountCents int64 `json:"rechargeAmountCents"` RechargeAmountCents int64 `json:"rechargeAmountCents"`
GoogleProductID string `json:"googleProductId"`
RewardGroupID flexibleInt64 `json:"rewardGroupId"` RewardGroupID flexibleInt64 `json:"rewardGroupId"`
RewardGroupName string `json:"rewardGroupName"` RewardGroupName string `json:"rewardGroupName"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
} }
type ConfigResponse struct { type ConfigResponse struct {
Configured bool `json:"configured"` Configured bool `json:"configured"`
ID int64 `json:"id,string"` ID int64 `json:"id,string"`
SysOrigin string `json:"sysOrigin"` SysOrigin string `json:"sysOrigin"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
UpdateTime string `json:"updateTime,omitempty"` MinAppVersion string `json:"minAppVersion,omitempty"`
LevelConfigs []LevelPayload `json:"levelConfigs"` UpdateTime string `json:"updateTime,omitempty"`
LevelConfigs []LevelPayload `json:"levelConfigs"`
} }
type SaveConfigRequest struct { type SaveConfigRequest struct {
ID flexibleInt64 `json:"id"` ID flexibleInt64 `json:"id"`
SysOrigin string `json:"sysOrigin"` SysOrigin string `json:"sysOrigin"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
LevelConfigs []LevelInput `json:"levelConfigs"` MinAppVersion string `json:"minAppVersion"`
LevelConfigs []LevelInput `json:"levelConfigs"`
} }
type HomeResponse struct { type HomeResponse struct {
@ -108,6 +112,7 @@ type HomeResponse struct {
HasRewardRecord bool `json:"hasRewardRecord"` HasRewardRecord bool `json:"hasRewardRecord"`
RewardStatus string `json:"rewardStatus,omitempty"` RewardStatus string `json:"rewardStatus,omitempty"`
Rewarded bool `json:"rewarded"` Rewarded bool `json:"rewarded"`
MinAppVersion string `json:"minAppVersion,omitempty"`
MatchedLevel int `json:"matchedLevel,omitempty"` MatchedLevel int `json:"matchedLevel,omitempty"`
RechargeAmount string `json:"rechargeAmount,omitempty"` RechargeAmount string `json:"rechargeAmount,omitempty"`
RechargeAmountCents int64 `json:"rechargeAmountCents,omitempty"` RechargeAmountCents int64 `json:"rechargeAmountCents,omitempty"`
@ -141,6 +146,7 @@ type GrantRecordView struct {
Level int `json:"level"` Level int `json:"level"`
PayPlatform string `json:"payPlatform"` PayPlatform string `json:"payPlatform"`
PaymentMethod string `json:"paymentMethod"` PaymentMethod string `json:"paymentMethod"`
GoogleProductID string `json:"googleProductId,omitempty"`
SourceOrderID string `json:"sourceOrderId,omitempty"` SourceOrderID string `json:"sourceOrderId,omitempty"`
RewardGroupID int64 `json:"rewardGroupId,string"` RewardGroupID int64 `json:"rewardGroupId,string"`
RewardGroupName string `json:"rewardGroupName,omitempty"` RewardGroupName string `json:"rewardGroupName,omitempty"`
@ -166,24 +172,26 @@ type RewardItem struct {
} }
type RechargeEvent struct { type RechargeEvent struct {
EventID string `json:"eventId"` EventID string `json:"eventId"`
SysOrigin string `json:"sysOrigin"` SysOrigin string `json:"sysOrigin"`
UserID flexibleInt64 `json:"userId"` UserID flexibleInt64 `json:"userId"`
AmountCents flexibleInt64 `json:"amountCents"` AmountCents flexibleInt64 `json:"amountCents"`
Amount flexibleString `json:"amount"` Amount flexibleString `json:"amount"`
AmountUSD flexibleString `json:"amountUsd"` AmountUSD flexibleString `json:"amountUsd"`
USDAmount flexibleString `json:"usdAmount"` USDAmount flexibleString `json:"usdAmount"`
Currency string `json:"currency"` Currency string `json:"currency"`
PayPlatform string `json:"payPlatform"` PayPlatform string `json:"payPlatform"`
PaymentMethod string `json:"paymentMethod"` PaymentMethod string `json:"paymentMethod"`
AppVersion flexibleString `json:"appVersion"` GoogleProductID string `json:"googleProductId"`
ClientVersion flexibleString `json:"clientVersion"` ProductID flexibleString `json:"productId"`
Version flexibleString `json:"version"` AppVersion flexibleString `json:"appVersion"`
ReqVersion flexibleString `json:"reqVersion"` ClientVersion flexibleString `json:"clientVersion"`
SourceOrderID flexibleString `json:"sourceOrderId"` Version flexibleString `json:"version"`
OrderID flexibleString `json:"orderId"` ReqVersion flexibleString `json:"reqVersion"`
OccurredAt flexibleString `json:"occurredAt"` SourceOrderID flexibleString `json:"sourceOrderId"`
Payload json.RawMessage `json:"payload,omitempty"` OrderID flexibleString `json:"orderId"`
OccurredAt flexibleString `json:"occurredAt"`
Payload json.RawMessage `json:"payload,omitempty"`
} }
type ProcessRechargeResponse struct { type ProcessRechargeResponse struct {
@ -200,32 +208,35 @@ type configBundle struct {
} }
type configSnapshot struct { type configSnapshot struct {
ID int64 ID int64
SysOrigin string SysOrigin string
Enabled bool Enabled bool
UpdateTime time.Time MinAppVersion string
UpdateTime time.Time
} }
type levelSnapshot struct { type levelSnapshot struct {
ID int64 ID int64
Level int Level int
RechargeAmountCents int64 RechargeAmountCents int64
GoogleProductID string
RewardGroupID int64 RewardGroupID int64
RewardGroupName string RewardGroupName string
Enabled bool Enabled bool
} }
type normalizedRechargeEvent struct { type normalizedRechargeEvent struct {
EventID string EventID string
SysOrigin string SysOrigin string
UserID int64 UserID int64
AmountCents int64 AmountCents int64
Currency string Currency string
PayPlatform string PayPlatform string
PaymentMethod string PaymentMethod string
AppVersion string AppVersion string
SourceOrderID string GoogleProductID string
OccurredAt time.Time SourceOrderID string
OccurredAt time.Time
} }
type flexibleInt64 int64 type flexibleInt64 int64

View File

@ -5,14 +5,18 @@ import (
"strings" "strings"
) )
const firstRechargeRewardMinAppVersion = "1.5.0" const defaultFirstRechargeRewardMinAppVersion = "1.5.0"
func isFirstRechargeRewardAppVersionSupported(appVersion string) bool { func isFirstRechargeRewardAppVersionSupported(appVersion string, minAppVersion string) bool {
appVersion = strings.TrimSpace(appVersion) appVersion = strings.TrimSpace(appVersion)
minAppVersion = strings.TrimSpace(minAppVersion)
if minAppVersion == "" {
minAppVersion = defaultFirstRechargeRewardMinAppVersion
}
if appVersion == "" { if appVersion == "" {
return false return false
} }
return compareAppVersion(appVersion, firstRechargeRewardMinAppVersion) >= 0 return compareAppVersion(appVersion, minAppVersion) >= 0
} }
func compareAppVersion(left, right string) int { func compareAppVersion(left, right string) int {

View File

@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS `first_recharge_reward_config` (
`id` bigint NOT NULL COMMENT '主键ID', `id` bigint NOT NULL COMMENT '主键ID',
`sys_origin` varchar(32) NOT NULL COMMENT '来源系统', `sys_origin` varchar(32) NOT NULL COMMENT '来源系统',
`enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用', `enabled` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否启用',
`min_app_version` varchar(32) NOT NULL DEFAULT '1.5.0' COMMENT '最低生效App版本',
`create_time` datetime(3) DEFAULT NULL COMMENT '创建时间', `create_time` datetime(3) DEFAULT NULL COMMENT '创建时间',
`update_time` datetime(3) DEFAULT NULL COMMENT '更新时间', `update_time` datetime(3) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@ -14,6 +15,7 @@ CREATE TABLE IF NOT EXISTS `first_recharge_reward_level` (
`config_id` bigint NOT NULL COMMENT '首冲奖励主配置ID', `config_id` bigint NOT NULL COMMENT '首冲奖励主配置ID',
`level` int NOT NULL COMMENT '档位', `level` int NOT NULL COMMENT '档位',
`recharge_amount_cents` bigint NOT NULL DEFAULT '0' COMMENT '充值金额门槛,单位为分', `recharge_amount_cents` bigint NOT NULL DEFAULT '0' COMMENT '充值金额门槛,单位为分',
`google_product_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Google商品ID',
`reward_group_id` bigint NOT NULL DEFAULT '0' COMMENT '奖励资源组ID', `reward_group_id` bigint NOT NULL DEFAULT '0' COMMENT '奖励资源组ID',
`reward_group_name` varchar(255) NOT NULL DEFAULT '' COMMENT '奖励资源组名称快照', `reward_group_name` varchar(255) NOT NULL DEFAULT '' COMMENT '奖励资源组名称快照',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用', `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
@ -22,6 +24,7 @@ CREATE TABLE IF NOT EXISTS `first_recharge_reward_level` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uk_first_recharge_reward_level` (`config_id`, `level`), UNIQUE KEY `uk_first_recharge_reward_level` (`config_id`, `level`),
UNIQUE KEY `uk_first_recharge_reward_amount` (`config_id`, `recharge_amount_cents`), UNIQUE KEY `uk_first_recharge_reward_amount` (`config_id`, `recharge_amount_cents`),
KEY `idx_first_recharge_reward_level_google_product` (`google_product_id`),
KEY `idx_first_recharge_reward_level_config` (`config_id`) KEY `idx_first_recharge_reward_level_config` (`config_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='首冲奖励充值档位'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='首冲奖励充值档位';
@ -42,6 +45,7 @@ CREATE TABLE IF NOT EXISTS `first_recharge_reward_grant_record` (
`level` int NOT NULL COMMENT '命中档位', `level` int NOT NULL COMMENT '命中档位',
`pay_platform` varchar(64) NOT NULL DEFAULT '' COMMENT '支付平台GOOGLE/MIFA_PAY/PAYER_MAX等', `pay_platform` varchar(64) NOT NULL DEFAULT '' COMMENT '支付平台GOOGLE/MIFA_PAY/PAYER_MAX等',
`payment_method` varchar(32) NOT NULL DEFAULT '' COMMENT '充值方式GOOGLE/THIRD_PARTY', `payment_method` varchar(32) NOT NULL DEFAULT '' COMMENT '充值方式GOOGLE/THIRD_PARTY',
`google_product_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Google商品ID',
`source_order_id` varchar(128) NOT NULL DEFAULT '' COMMENT '来源订单ID', `source_order_id` varchar(128) NOT NULL DEFAULT '' COMMENT '来源订单ID',
`reward_group_id` bigint NOT NULL DEFAULT '0' COMMENT '奖励资源组ID', `reward_group_id` bigint NOT NULL DEFAULT '0' COMMENT '奖励资源组ID',
`reward_group_name` varchar(255) NOT NULL DEFAULT '' COMMENT '奖励资源组名称快照', `reward_group_name` varchar(255) NOT NULL DEFAULT '' COMMENT '奖励资源组名称快照',
@ -58,5 +62,6 @@ CREATE TABLE IF NOT EXISTS `first_recharge_reward_grant_record` (
UNIQUE KEY `uk_first_recharge_reward_event` (`event_id`), UNIQUE KEY `uk_first_recharge_reward_event` (`event_id`),
UNIQUE KEY `uk_first_recharge_reward_user` (`sys_origin`, `user_id`), UNIQUE KEY `uk_first_recharge_reward_user` (`sys_origin`, `user_id`),
KEY `idx_first_recharge_reward_status` (`sys_origin`, `user_id`, `status`), KEY `idx_first_recharge_reward_status` (`sys_origin`, `user_id`, `status`),
KEY `idx_first_recharge_reward_grant_google_product` (`google_product_id`),
KEY `idx_first_recharge_reward_order` (`source_order_id`) KEY `idx_first_recharge_reward_order` (`source_order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='首冲奖励发放记录'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='首冲奖励发放记录';

View File

@ -0,0 +1,71 @@
SET @current_schema := DATABASE();
SET @add_first_recharge_reward_min_app_version_sql := IF(
NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = @current_schema
AND table_name = 'first_recharge_reward_config'
AND column_name = 'min_app_version'
),
'ALTER TABLE `first_recharge_reward_config` ADD COLUMN `min_app_version` varchar(32) NOT NULL DEFAULT ''1.5.0'' COMMENT ''最低生效App版本'' AFTER `enabled`',
'SELECT 1'
);
PREPARE add_first_recharge_reward_min_app_version_stmt FROM @add_first_recharge_reward_min_app_version_sql;
EXECUTE add_first_recharge_reward_min_app_version_stmt;
DEALLOCATE PREPARE add_first_recharge_reward_min_app_version_stmt;
SET @add_first_recharge_reward_level_google_product_sql := IF(
NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = @current_schema
AND table_name = 'first_recharge_reward_level'
AND column_name = 'google_product_id'
),
'ALTER TABLE `first_recharge_reward_level` ADD COLUMN `google_product_id` varchar(128) NOT NULL DEFAULT '''' COMMENT ''Google商品ID'' AFTER `recharge_amount_cents`',
'SELECT 1'
);
PREPARE add_first_recharge_reward_level_google_product_stmt FROM @add_first_recharge_reward_level_google_product_sql;
EXECUTE add_first_recharge_reward_level_google_product_stmt;
DEALLOCATE PREPARE add_first_recharge_reward_level_google_product_stmt;
SET @add_first_recharge_reward_record_google_product_sql := IF(
NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_schema = @current_schema
AND table_name = 'first_recharge_reward_grant_record'
AND column_name = 'google_product_id'
),
'ALTER TABLE `first_recharge_reward_grant_record` ADD COLUMN `google_product_id` varchar(128) NOT NULL DEFAULT '''' COMMENT ''Google商品ID'' AFTER `payment_method`',
'SELECT 1'
);
PREPARE add_first_recharge_reward_record_google_product_stmt FROM @add_first_recharge_reward_record_google_product_sql;
EXECUTE add_first_recharge_reward_record_google_product_stmt;
DEALLOCATE PREPARE add_first_recharge_reward_record_google_product_stmt;
SET @add_first_recharge_reward_level_google_product_idx_sql := IF(
NOT EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = @current_schema
AND table_name = 'first_recharge_reward_level'
AND index_name = 'idx_first_recharge_reward_level_google_product'
),
'ALTER TABLE `first_recharge_reward_level` ADD INDEX `idx_first_recharge_reward_level_google_product` (`google_product_id`)',
'SELECT 1'
);
PREPARE add_first_recharge_reward_level_google_product_idx_stmt FROM @add_first_recharge_reward_level_google_product_idx_sql;
EXECUTE add_first_recharge_reward_level_google_product_idx_stmt;
DEALLOCATE PREPARE add_first_recharge_reward_level_google_product_idx_stmt;
SET @add_first_recharge_reward_record_google_product_idx_sql := IF(
NOT EXISTS (
SELECT 1 FROM information_schema.statistics
WHERE table_schema = @current_schema
AND table_name = 'first_recharge_reward_grant_record'
AND index_name = 'idx_first_recharge_reward_grant_google_product'
),
'ALTER TABLE `first_recharge_reward_grant_record` ADD INDEX `idx_first_recharge_reward_grant_google_product` (`google_product_id`)',
'SELECT 1'
);
PREPARE add_first_recharge_reward_record_google_product_idx_stmt FROM @add_first_recharge_reward_record_google_product_idx_sql;
EXECUTE add_first_recharge_reward_record_google_product_idx_stmt;
DEALLOCATE PREPARE add_first_recharge_reward_record_google_product_idx_stmt;