diff --git a/internal/module/client/entity/types.go b/internal/module/client/entity/types.go index dcd8350..de5a719 100644 --- a/internal/module/client/entity/types.go +++ b/internal/module/client/entity/types.go @@ -1,6 +1,9 @@ package entity -import "time" +import ( + "strings" + "time" +) const ( DocPrivacy = "privacy" @@ -41,14 +44,33 @@ type Feedback struct { CreatedAt time.Time `json:"created_at"` } +// CanonicalHealthPackage maps the health.wyrs.com product name onto the iOS bundle already stored in admin. +func CanonicalHealthPackage(pkg string) string { + pkg = strings.TrimSpace(pkg) + if strings.EqualFold(pkg, "health.wyrs.com") { + return "health.wyrs365.com" + } + return pkg +} + +const ( + // PlatformIOS / PlatformAndroid / PlatformWeb split one duration into three prices. + // Empty platform is legacy data and still matches every client platform. + PlatformIOS = "ios" + PlatformAndroid = "android" + PlatformWeb = "web" +) + // SubscriptionPlan is one week/month/quarter/year card. // IAP product_id placeholders: health.wyrs365.com.vip.{week|month|quarter|year} // (ASC App 6815533078 · Team 49S9UL5MN4 · Bundle health.wyrs365.com). // PackageName 为空表示默认兜底;客户端传 packageName / X-Wyrs-Package 时优先精确匹配。 +// Platform 为 ios / android / web;空字符串是改平台前的旧行。 type SubscriptionPlan struct { PlanID string `json:"plan_id"` Lang string `json:"lang"` PackageName string `json:"package_name,omitempty"` + Platform string `json:"platform,omitempty"` Title string `json:"title"` Subtitle string `json:"subtitle"` PriceDisplay string `json:"price_display"` diff --git a/internal/module/client/logic/service.go b/internal/module/client/logic/service.go index 5e2f237..23a290f 100644 --- a/internal/module/client/logic/service.go +++ b/internal/module/client/logic/service.go @@ -183,30 +183,28 @@ func (s *Service) UpdateFeedbackStatus(ctx context.Context, id int64, status str return s.repo.UpdateFeedbackStatus(ctx, id, status) } -func (s *Service) SubscriptionPlans(ctx context.Context, packageName string) (*entity.SubscriptionConfig, error) { +func (s *Service) SubscriptionPlans(ctx context.Context, packageName, platform string) (*entity.SubscriptionConfig, error) { lang := normalizeLang(reqctx.Language(ctx)) - packageName = strings.TrimSpace(packageName) + packageName = canonicalHealthPackage(packageName) + platform = normalizePlatform(platform) headline, sub := subscriptionCopy(lang) if s.repo != nil { - plans, err := s.repo.ListPlans(ctx, lang, packageName) - if err == nil && len(plans) > 0 { - return &entity.SubscriptionConfig{Headline: headline, Subheadline: sub, Plans: plans}, nil - } - if lang != string(i18n.ZhCN) { - plans, err = s.repo.ListPlans(ctx, string(i18n.ZhCN), packageName) - if err == nil && len(plans) > 0 { - return &entity.SubscriptionConfig{Headline: headline, Subheadline: sub, Plans: plans}, nil - } + plans, err := s.clientPlans(ctx, lang, packageName, platform) + if err != nil { + return nil, err } + return &entity.SubscriptionConfig{Headline: headline, Subheadline: sub, Plans: plans}, nil } - return &entity.SubscriptionConfig{Headline: headline, Subheadline: sub, Plans: defaultPlans(lang)}, nil + return &entity.SubscriptionConfig{ + Headline: headline, Subheadline: sub, Plans: filterPlansByPlatform(defaultPlans(lang), platform), + }, nil } func (s *Service) ListPlansAdmin(ctx context.Context, lang, packageName string) ([]entity.SubscriptionPlan, error) { if s.repo == nil { return defaultPlans(normalizeLang(lang)), nil } - return s.repo.ListPlansAdmin(ctx, strings.TrimSpace(lang), strings.TrimSpace(packageName)) + return s.repo.ListPlansAdmin(ctx, strings.TrimSpace(lang), canonicalHealthPackage(packageName)) } func (s *Service) UpsertPlanAdmin(ctx context.Context, p *entity.SubscriptionPlan) error { @@ -218,7 +216,12 @@ func (s *Service) UpsertPlanAdmin(ctx context.Context, p *entity.SubscriptionPla } p.Lang = normalizeLang(p.Lang) p.PlanID = strings.TrimSpace(p.PlanID) - p.PackageName = strings.TrimSpace(p.PackageName) + p.PackageName = canonicalHealthPackage(p.PackageName) + rawPlatform := strings.TrimSpace(p.Platform) + p.Platform = normalizePlatform(rawPlatform) + if rawPlatform != "" && p.Platform == "" { + return errs.ErrInvalidArgument.WithMsg("platform must be ios|android|web") + } switch p.PlanID { case "week", "month", "quarter", "year": default: diff --git a/internal/module/client/model/repo.go b/internal/module/client/model/repo.go index a573c4d..5fb6e67 100644 --- a/internal/module/client/model/repo.go +++ b/internal/module/client/model/repo.go @@ -52,6 +52,7 @@ type planRow struct { PlanID string `gorm:"column:plan_id;primaryKey"` Lang string `gorm:"column:lang;primaryKey"` PackageName string `gorm:"column:package_name;primaryKey"` + Platform string `gorm:"column:platform;primaryKey"` Title string `gorm:"column:title"` Subtitle string `gorm:"column:subtitle"` PriceDisplay string `gorm:"column:price_display"` @@ -204,7 +205,7 @@ func mapPlans(rows []planRow) []entity.SubscriptionPlan { for i := range rows { row := &rows[i] out = append(out, entity.SubscriptionPlan{ - PlanID: row.PlanID, Lang: row.Lang, PackageName: row.PackageName, + PlanID: row.PlanID, Lang: row.Lang, PackageName: row.PackageName, Platform: row.Platform, Title: row.Title, Subtitle: row.Subtitle, PriceDisplay: row.PriceDisplay, PriceCents: row.PriceCents, Currency: row.Currency, PeriodDays: row.PeriodDays, Badge: row.Badge, SortOrder: row.SortOrder, @@ -230,11 +231,11 @@ func filterPlansByPackage(items []entity.SubscriptionPlan, packageName string) [ } return items } - want := strings.ToLower(packageName) + want := strings.ToLower(entity.CanonicalHealthPackage(packageName)) exact := make([]entity.SubscriptionPlan, 0) fallback := make([]entity.SubscriptionPlan, 0) for _, p := range items { - pkg := strings.TrimSpace(p.PackageName) + pkg := entity.CanonicalHealthPackage(p.PackageName) if pkg != "" && strings.ToLower(pkg) == want { exact = append(exact, p) } else if pkg == "" { @@ -252,7 +253,7 @@ func (r *Repo) UpsertPlan(ctx context.Context, p *entity.SubscriptionPlan) error return errors.New("client: nil subscription plan") } row := planRow{ - PlanID: p.PlanID, Lang: p.Lang, PackageName: strings.TrimSpace(p.PackageName), + PlanID: p.PlanID, Lang: p.Lang, PackageName: strings.TrimSpace(p.PackageName), Platform: p.Platform, Title: p.Title, Subtitle: p.Subtitle, PriceDisplay: p.PriceDisplay, PriceCents: p.PriceCents, Currency: p.Currency, PeriodDays: p.PeriodDays, Badge: p.Badge, SortOrder: p.SortOrder, diff --git a/internal/transport/http/handler/client/handler.go b/internal/transport/http/handler/client/handler.go index b5277ae..a9be761 100644 --- a/internal/transport/http/handler/client/handler.go +++ b/internal/transport/http/handler/client/handler.go @@ -48,7 +48,11 @@ func (h *Handler) SubscriptionPlans(c *gin.Context) { if pkg == "" { pkg = strings.TrimSpace(c.GetHeader("X-Wyrs-Package")) } - out, err := h.svc.SubscriptionPlans(c.Request.Context(), pkg) + platform := strings.TrimSpace(c.Query("platform")) + if platform == "" { + platform = strings.TrimSpace(c.GetHeader("X-Wyrs-Platform")) + } + out, err := h.svc.SubscriptionPlans(c.Request.Context(), pkg, platform) if err != nil { respond.JSONError(c, err) return