feat(client): 按平台拆分会员价格,并归一 health 包名
同一时长可按 ios、android、web 分别定价;health.wyrs.com 对齐已入库的 health.wyrs365.com。
This commit is contained in:
parent
1bbf3b97c7
commit
c4d1322cec
@ -1,6 +1,9 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DocPrivacy = "privacy"
|
DocPrivacy = "privacy"
|
||||||
@ -41,14 +44,33 @@ type Feedback struct {
|
|||||||
CreatedAt time.Time `json:"created_at"`
|
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.
|
// SubscriptionPlan is one week/month/quarter/year card.
|
||||||
// IAP product_id placeholders: health.wyrs365.com.vip.{week|month|quarter|year}
|
// IAP product_id placeholders: health.wyrs365.com.vip.{week|month|quarter|year}
|
||||||
// (ASC App 6815533078 · Team 49S9UL5MN4 · Bundle health.wyrs365.com).
|
// (ASC App 6815533078 · Team 49S9UL5MN4 · Bundle health.wyrs365.com).
|
||||||
// PackageName 为空表示默认兜底;客户端传 packageName / X-Wyrs-Package 时优先精确匹配。
|
// PackageName 为空表示默认兜底;客户端传 packageName / X-Wyrs-Package 时优先精确匹配。
|
||||||
|
// Platform 为 ios / android / web;空字符串是改平台前的旧行。
|
||||||
type SubscriptionPlan struct {
|
type SubscriptionPlan struct {
|
||||||
PlanID string `json:"plan_id"`
|
PlanID string `json:"plan_id"`
|
||||||
Lang string `json:"lang"`
|
Lang string `json:"lang"`
|
||||||
PackageName string `json:"package_name,omitempty"`
|
PackageName string `json:"package_name,omitempty"`
|
||||||
|
Platform string `json:"platform,omitempty"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Subtitle string `json:"subtitle"`
|
Subtitle string `json:"subtitle"`
|
||||||
PriceDisplay string `json:"price_display"`
|
PriceDisplay string `json:"price_display"`
|
||||||
|
|||||||
@ -183,30 +183,28 @@ func (s *Service) UpdateFeedbackStatus(ctx context.Context, id int64, status str
|
|||||||
return s.repo.UpdateFeedbackStatus(ctx, id, status)
|
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))
|
lang := normalizeLang(reqctx.Language(ctx))
|
||||||
packageName = strings.TrimSpace(packageName)
|
packageName = canonicalHealthPackage(packageName)
|
||||||
|
platform = normalizePlatform(platform)
|
||||||
headline, sub := subscriptionCopy(lang)
|
headline, sub := subscriptionCopy(lang)
|
||||||
if s.repo != nil {
|
if s.repo != nil {
|
||||||
plans, err := s.repo.ListPlans(ctx, lang, packageName)
|
plans, err := s.clientPlans(ctx, lang, packageName, platform)
|
||||||
if err == nil && len(plans) > 0 {
|
if err != nil {
|
||||||
return &entity.SubscriptionConfig{Headline: headline, Subheadline: sub, Plans: plans}, nil
|
return nil, err
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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) {
|
func (s *Service) ListPlansAdmin(ctx context.Context, lang, packageName string) ([]entity.SubscriptionPlan, error) {
|
||||||
if s.repo == nil {
|
if s.repo == nil {
|
||||||
return defaultPlans(normalizeLang(lang)), 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 {
|
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.Lang = normalizeLang(p.Lang)
|
||||||
p.PlanID = strings.TrimSpace(p.PlanID)
|
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 {
|
switch p.PlanID {
|
||||||
case "week", "month", "quarter", "year":
|
case "week", "month", "quarter", "year":
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -52,6 +52,7 @@ type planRow struct {
|
|||||||
PlanID string `gorm:"column:plan_id;primaryKey"`
|
PlanID string `gorm:"column:plan_id;primaryKey"`
|
||||||
Lang string `gorm:"column:lang;primaryKey"`
|
Lang string `gorm:"column:lang;primaryKey"`
|
||||||
PackageName string `gorm:"column:package_name;primaryKey"`
|
PackageName string `gorm:"column:package_name;primaryKey"`
|
||||||
|
Platform string `gorm:"column:platform;primaryKey"`
|
||||||
Title string `gorm:"column:title"`
|
Title string `gorm:"column:title"`
|
||||||
Subtitle string `gorm:"column:subtitle"`
|
Subtitle string `gorm:"column:subtitle"`
|
||||||
PriceDisplay string `gorm:"column:price_display"`
|
PriceDisplay string `gorm:"column:price_display"`
|
||||||
@ -204,7 +205,7 @@ func mapPlans(rows []planRow) []entity.SubscriptionPlan {
|
|||||||
for i := range rows {
|
for i := range rows {
|
||||||
row := &rows[i]
|
row := &rows[i]
|
||||||
out = append(out, entity.SubscriptionPlan{
|
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,
|
Title: row.Title, Subtitle: row.Subtitle,
|
||||||
PriceDisplay: row.PriceDisplay, PriceCents: row.PriceCents, Currency: row.Currency,
|
PriceDisplay: row.PriceDisplay, PriceCents: row.PriceCents, Currency: row.Currency,
|
||||||
PeriodDays: row.PeriodDays, Badge: row.Badge, SortOrder: row.SortOrder,
|
PeriodDays: row.PeriodDays, Badge: row.Badge, SortOrder: row.SortOrder,
|
||||||
@ -230,11 +231,11 @@ func filterPlansByPackage(items []entity.SubscriptionPlan, packageName string) [
|
|||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
want := strings.ToLower(packageName)
|
want := strings.ToLower(entity.CanonicalHealthPackage(packageName))
|
||||||
exact := make([]entity.SubscriptionPlan, 0)
|
exact := make([]entity.SubscriptionPlan, 0)
|
||||||
fallback := make([]entity.SubscriptionPlan, 0)
|
fallback := make([]entity.SubscriptionPlan, 0)
|
||||||
for _, p := range items {
|
for _, p := range items {
|
||||||
pkg := strings.TrimSpace(p.PackageName)
|
pkg := entity.CanonicalHealthPackage(p.PackageName)
|
||||||
if pkg != "" && strings.ToLower(pkg) == want {
|
if pkg != "" && strings.ToLower(pkg) == want {
|
||||||
exact = append(exact, p)
|
exact = append(exact, p)
|
||||||
} else if pkg == "" {
|
} 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")
|
return errors.New("client: nil subscription plan")
|
||||||
}
|
}
|
||||||
row := planRow{
|
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,
|
Title: p.Title, Subtitle: p.Subtitle,
|
||||||
PriceDisplay: p.PriceDisplay, PriceCents: p.PriceCents, Currency: p.Currency,
|
PriceDisplay: p.PriceDisplay, PriceCents: p.PriceCents, Currency: p.Currency,
|
||||||
PeriodDays: p.PeriodDays, Badge: p.Badge, SortOrder: p.SortOrder,
|
PeriodDays: p.PeriodDays, Badge: p.Badge, SortOrder: p.SortOrder,
|
||||||
|
|||||||
@ -48,7 +48,11 @@ func (h *Handler) SubscriptionPlans(c *gin.Context) {
|
|||||||
if pkg == "" {
|
if pkg == "" {
|
||||||
pkg = strings.TrimSpace(c.GetHeader("X-Wyrs-Package"))
|
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 {
|
if err != nil {
|
||||||
respond.JSONError(c, err)
|
respond.JSONError(c, err)
|
||||||
return
|
return
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user