增加首冲
This commit is contained in:
parent
e738fbc44a
commit
b53313f62d
@ -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"`
|
||||||
|
|||||||
@ -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 ""
|
||||||
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 ""
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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{})
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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='首冲奖励发放记录';
|
||||||
|
|||||||
@ -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;
|
||||||
Loading…
x
Reference in New Issue
Block a user