diff --git a/deploy/popadmin/popadmin.js b/deploy/popadmin/popadmin.js
index babd7be..2ad6123 100644
--- a/deploy/popadmin/popadmin.js
+++ b/deploy/popadmin/popadmin.js
@@ -3087,45 +3087,122 @@ const pages = {
},
async subPlans() {
+ // SleepHeart ASC App 6815533078 · Team 49S9UL5MN4 · Bundle health.wyrs365.com
+ const PLAN_DEFAULTS = {
+ week: { title: '周卡', subtitle: '灵活体验,随时可停', price_display: '$1/周', price_cents: 100, period_days: 7, badge: '', sort_order: 1, product_id: 'health.wyrs365.com.vip.week' },
+ month: { title: '月卡', subtitle: '最受欢迎', price_display: '$3/月', price_cents: 300, period_days: 30, badge: '热门', sort_order: 2, product_id: 'health.wyrs365.com.vip.month' },
+ quarter: { title: '季卡', subtitle: '平均每月更省', price_display: '$8/季', price_cents: 800, period_days: 90, badge: '', sort_order: 3, product_id: 'health.wyrs365.com.vip.quarter' },
+ year: { title: '年卡', subtitle: '全年畅听会员曲库', price_display: '$12/年', price_cents: 1200, period_days: 365, badge: '超值', sort_order: 4, product_id: 'health.wyrs365.com.vip.year' }
+ };
let items = [];
try {
- const data = await api('/spark/subscriptions/plans?lang=zh-CN');
+ const data = await api('/spark/subscriptions/plans');
items = (data && data.items) || [];
} catch (e) {
$('content').innerHTML = `
订阅配置 API:${esc(e.message)}
`;
return;
}
+ const fillForm = (p) => {
+ if (!p) return;
+ $('spPlan').value = p.plan_id || 'week';
+ $('spLang').value = p.lang || 'zh-CN';
+ $('spTitle').value = p.title || '';
+ $('spSub').value = p.subtitle || '';
+ $('spPrice').value = p.price_display || '';
+ $('spCents').value = p.price_cents != null ? p.price_cents : '';
+ $('spCurrency').value = p.currency || 'USD';
+ $('spDays').value = p.period_days != null ? p.period_days : 7;
+ $('spBadge').value = p.badge || '';
+ $('spProduct').value = p.product_id || '';
+ $('spSort').value = p.sort_order != null ? p.sort_order : 1;
+ $('spEnabled').checked = p.enabled !== false && p.enabled !== 0;
+ };
+ const applyPlanDefaults = () => {
+ const d = PLAN_DEFAULTS[$('spPlan').value] || PLAN_DEFAULTS.week;
+ if (!$('spTitle').value) $('spTitle').value = d.title;
+ if (!$('spSub').value) $('spSub').value = d.subtitle;
+ if (!$('spPrice').value) $('spPrice').value = d.price_display;
+ if (!$('spCents').value) $('spCents').value = d.price_cents;
+ if (!$('spDays').value) $('spDays').value = d.period_days;
+ if (!$('spBadge').value && d.badge) $('spBadge').value = d.badge;
+ if (!$('spProduct').value) $('spProduct').value = d.product_id;
+ if (!$('spSort').value) $('spSort').value = d.sort_order;
+ $('spCurrency').value = 'USD';
+ };
$('content').innerHTML = `
- SleepHeart 订阅引导 · /api/admin/spark/subscriptions/plans · 客户端 GET /v1/subscriptions/plans · plan_id=week|month|quarter|year
+ SleepHeart 订阅引导 · ASC App 6815533078 · Team 49S9UL5MN4 · Bundle health.wyrs365.com
+ Admin /api/admin/spark/subscriptions/plans · 客户端 GET /v1/subscriptions/plans · 默认 USD $1/周 · $3/月 · $12/年(季卡 $8 可保留)· IAP health.wyrs365.com.vip.{week|month|quarter|year}
-
-
+
+
+
-
+
+
- | plan | lang | 标题 | 价格 | 天数 | 角标 | product | 启用 |
+ | plan | lang | 标题 | 价格 | 币种 | 天数 | 角标 | product | 启用 |
- ${items.length ? items.map((p)=>`| ${esc(p.plan_id)} | ${esc(p.lang)} | ${esc(p.title)} ${esc(p.subtitle||'')} | ${esc(p.price_display)} (${esc(p.price_cents)}) | ${esc(p.period_days)} | ${esc(p.badge||'')} | ${esc(p.product_id)} | ${esc(p.enabled)} |
`).join('') : '| 暂无套餐 |
'}
+ ${items.length ? items.map((p, i)=>`| ${esc(p.plan_id)} | ${esc(p.lang)} | ${esc(p.title)} ${esc(p.subtitle||'')} | ${esc(p.price_display)} (${esc(p.price_cents)}) | ${esc(p.currency||'USD')} | ${esc(p.period_days)} | ${esc(p.badge||'')} | ${esc(p.product_id)} | ${esc(p.enabled)} |
`).join('') : '| 暂无套餐(可点「填入默认价」后保存) |
'}
`;
+ const seed = items.find((p) => p.plan_id === 'month' && p.lang === 'zh-CN')
+ || items.find((p) => p.lang === 'zh-CN')
+ || items[0];
+ if (seed) fillForm(seed);
+ else applyPlanDefaults();
+ $('spPlan').onchange = () => {
+ const hit = items.find((p) => p.plan_id === $('spPlan').value && p.lang === $('spLang').value);
+ if (hit) fillForm(hit);
+ else {
+ $('spTitle').value = ''; $('spSub').value = ''; $('spPrice').value = '';
+ $('spCents').value = ''; $('spBadge').value = ''; $('spProduct').value = '';
+ applyPlanDefaults();
+ }
+ };
+ $('spLang').onchange = () => {
+ const hit = items.find((p) => p.plan_id === $('spPlan').value && p.lang === $('spLang').value);
+ if (hit) fillForm(hit);
+ };
+ $('spFillDefaults').onclick = () => {
+ const d = PLAN_DEFAULTS[$('spPlan').value] || PLAN_DEFAULTS.week;
+ $('spTitle').value = d.title;
+ $('spSub').value = d.subtitle;
+ $('spPrice').value = d.price_display;
+ $('spCents').value = d.price_cents;
+ $('spCurrency').value = 'USD';
+ $('spDays').value = d.period_days;
+ $('spBadge').value = d.badge;
+ $('spProduct').value = d.product_id;
+ $('spSort').value = d.sort_order;
+ $('spEnabled').checked = true;
+ };
+ $('content').querySelectorAll('[data-sp-idx]').forEach((tr) => {
+ tr.onclick = () => fillForm(items[parseInt(tr.getAttribute('data-sp-idx'), 10)]);
+ });
$('spSave').onclick = async () => {
+ const planId = $('spPlan').value;
+ const d = PLAN_DEFAULTS[planId] || PLAN_DEFAULTS.week;
await api('/spark/subscriptions/plans', { method: 'PUT', body: JSON.stringify({
- plan_id: $('spPlan').value, lang: $('spLang').value, title: $('spTitle').value.trim(),
- subtitle: $('spSub').value.trim(), price_display: $('spPrice').value.trim(),
- price_cents: parseInt($('spCents').value,10)||0, currency: 'CNY',
- period_days: parseInt($('spDays').value,10)||7, badge: $('spBadge').value.trim(),
- sort_order: parseInt($('spSort').value,10)||1, enabled: !!$('spEnabled').checked,
- product_id: $('spProduct').value.trim()
+ plan_id: planId, lang: $('spLang').value, title: $('spTitle').value.trim() || d.title,
+ subtitle: $('spSub').value.trim() || d.subtitle,
+ price_display: $('spPrice').value.trim() || d.price_display,
+ price_cents: parseInt($('spCents').value, 10) || d.price_cents,
+ currency: $('spCurrency').value || 'USD',
+ period_days: parseInt($('spDays').value, 10) || d.period_days,
+ badge: $('spBadge').value.trim(),
+ sort_order: parseInt($('spSort').value, 10) || d.sort_order,
+ enabled: !!$('spEnabled').checked,
+ product_id: $('spProduct').value.trim() || d.product_id
})});
pages.subPlans();
};
diff --git a/docs/daily/2026-09-24-change-list-sleepheart.md b/docs/daily/2026-09-24-change-list-sleepheart.md
index 26ef900..7f54b31 100644
--- a/docs/daily/2026-09-24-change-list-sleepheart.md
+++ b/docs/daily/2026-09-24-change-list-sleepheart.md
@@ -4,8 +4,9 @@
### 概要
-- App 展示名统一为 **SleepHeart**;Bundle ID 保持 `health.wyrs365.com`
+- App 展示名统一为 **SleepHeart**;Bundle ID 保持 `health.wyrs365.com`;ASC App `6815533078`;Team `49S9UL5MN4`
- spark-server 新增法律文档 / 反馈 / 订阅套餐 / 密码登录 API,并在 popadmin 增加管理页
+- 订阅默认价 **USD $1/周 · $3/月 · $12/年**(保留季卡 $8);IAP `health.wyrs365.com.vip.{week|month|quarter|year}`
- HealthiOS 将原先 mock 的密码登录、反馈、订阅配置改为对接真接口(失败仍可 mock 回退)
### 变更范围
@@ -19,10 +20,11 @@
### 主要文件
- `migrations/0023_client_legal_feedback_subscription.sql` — 表与种子数据
+- `migrations/0025_subscription_plans_usd_pricing.sql` — USD 定价与 IAP product_id 对齐
- `internal/module/client/**` — 业务与仓储
- `internal/transport/http/handler/client`、`handler/admin/client.go`、`router*.go`、`auth.go`
-- `deploy/popadmin/popadmin.js` — 三页管理 UI
-- `HealthiOS` pbxproj / L10n / AuthService / APIModels
+- `deploy/popadmin/popadmin.js` — 三页管理 UI(订阅配置可编辑默认价)
+- `HealthiOS` pbxproj / L10n / AuthService / APIModels / PaywallView
### 验证
diff --git a/internal/module/client/entity/types.go b/internal/module/client/entity/types.go
index 4032d9d..85bf2a6 100644
--- a/internal/module/client/entity/types.go
+++ b/internal/module/client/entity/types.go
@@ -42,6 +42,8 @@ type Feedback struct {
}
// 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).
type SubscriptionPlan struct {
PlanID string `json:"plan_id"`
Lang string `json:"lang"`
diff --git a/internal/module/client/logic/service.go b/internal/module/client/logic/service.go
index 9b6c8c0..7872416 100644
--- a/internal/module/client/logic/service.go
+++ b/internal/module/client/logic/service.go
@@ -332,25 +332,39 @@ func subscriptionCopy(lang string) (headline, sub string) {
}
func defaultPlans(lang string) []entity.SubscriptionPlan {
+ const (
+ pidWeek = "health.wyrs365.com.vip.week"
+ pidMonth = "health.wyrs365.com.vip.month"
+ pidQuarter = "health.wyrs365.com.vip.quarter"
+ pidYear = "health.wyrs365.com.vip.year"
+ )
mk := func(id, title, sub, price, badge, product string, cents, days, order int) entity.SubscriptionPlan {
return entity.SubscriptionPlan{
PlanID: id, Lang: lang, Title: title, Subtitle: sub, PriceDisplay: price,
- PriceCents: cents, Currency: "CNY", PeriodDays: days, Badge: badge,
+ PriceCents: cents, Currency: "USD", PeriodDays: days, Badge: badge,
SortOrder: order, Enabled: true, ProductID: product,
}
}
if lang == "en" {
return []entity.SubscriptionPlan{
- mk("week", "Weekly", "Flexible", "$1.99", "", "health.sub.week", 199, 7, 1),
- mk("month", "Monthly", "Most popular", "$4.99", "Popular", "health.sub.month", 499, 30, 2),
- mk("quarter", "Quarterly", "Better value", "$12.99", "", "health.sub.quarter", 1299, 90, 3),
- mk("year", "Yearly", "Best value", "$29.99", "Best", "health.sub.year", 2999, 365, 4),
+ mk("week", "Weekly", "Flexible, cancel anytime", "$1/week", "", pidWeek, 100, 7, 1),
+ mk("month", "Monthly", "Most popular", "$3/month", "Popular", pidMonth, 300, 30, 2),
+ mk("quarter", "Quarterly", "Better monthly value", "$8/quarter", "", pidQuarter, 800, 90, 3),
+ mk("year", "Yearly", "Best value all year", "$12/year", "Best", pidYear, 1200, 365, 4),
+ }
+ }
+ if lang == "zh-TW" {
+ return []entity.SubscriptionPlan{
+ mk("week", "週卡", "靈活體驗,隨時可停", "$1/週", "", pidWeek, 100, 7, 1),
+ mk("month", "月卡", "最受歡迎", "$3/月", "熱門", pidMonth, 300, 30, 2),
+ mk("quarter", "季卡", "平均每月更省", "$8/季", "", pidQuarter, 800, 90, 3),
+ mk("year", "年卡", "全年暢聽會員曲庫", "$12/年", "超值", pidYear, 1200, 365, 4),
}
}
return []entity.SubscriptionPlan{
- mk("week", "周卡", "灵活体验", "¥12", "", "health.sub.week", 1200, 7, 1),
- mk("month", "月卡", "最受欢迎", "¥30", "热门", "health.sub.month", 3000, 30, 2),
- mk("quarter", "季卡", "更省", "¥68", "", "health.sub.quarter", 6800, 90, 3),
- mk("year", "年卡", "全年畅听", "¥198", "超值", "health.sub.year", 19800, 365, 4),
+ mk("week", "周卡", "灵活体验,随时可停", "$1/周", "", pidWeek, 100, 7, 1),
+ mk("month", "月卡", "最受欢迎", "$3/月", "热门", pidMonth, 300, 30, 2),
+ mk("quarter", "季卡", "平均每月更省", "$8/季", "", pidQuarter, 800, 90, 3),
+ mk("year", "年卡", "全年畅听会员曲库", "$12/年", "超值", pidYear, 1200, 365, 4),
}
}
diff --git a/internal/module/client/model/repo.go b/internal/module/client/model/repo.go
index c11eefd..225cc4f 100644
--- a/internal/module/client/model/repo.go
+++ b/internal/module/client/model/repo.go
@@ -219,7 +219,7 @@ func (r *Repo) UpsertPlan(ctx context.Context, p *entity.SubscriptionPlan) error
Enabled: p.Enabled, ProductID: p.ProductID, UpdatedAt: time.Now(),
}
if row.Currency == "" {
- row.Currency = "CNY"
+ row.Currency = "USD"
}
return r.db.WithContext(ctx).Save(&row).Error
}
diff --git a/migrations/0025_subscription_plans_usd_pricing.sql b/migrations/0025_subscription_plans_usd_pricing.sql
new file mode 100644
index 0000000..f278c7b
--- /dev/null
+++ b/migrations/0025_subscription_plans_usd_pricing.sql
@@ -0,0 +1,103 @@
+-- SleepHeart 订阅定价对齐:USD $1/周、$3/月、$12/年(保留季卡 $8);IAP product_id 占位。
+-- ASC App 6815533078 · Team 49S9UL5MN4 · Bundle health.wyrs365.com
+
+UPDATE t_subscription_plans
+SET price_display = CASE plan_id
+ WHEN 'week' THEN '$1/周'
+ WHEN 'month' THEN '$3/月'
+ WHEN 'quarter' THEN '$8/季'
+ WHEN 'year' THEN '$12/年'
+ ELSE price_display
+ END,
+ price_cents = CASE plan_id
+ WHEN 'week' THEN 100
+ WHEN 'month' THEN 300
+ WHEN 'quarter' THEN 800
+ WHEN 'year' THEN 1200
+ ELSE price_cents
+ END,
+ currency = 'USD',
+ product_id = CASE plan_id
+ WHEN 'week' THEN 'health.wyrs365.com.vip.week'
+ WHEN 'month' THEN 'health.wyrs365.com.vip.month'
+ WHEN 'quarter' THEN 'health.wyrs365.com.vip.quarter'
+ WHEN 'year' THEN 'health.wyrs365.com.vip.year'
+ ELSE product_id
+ END,
+ updated_at = CURRENT_TIMESTAMP(3)
+WHERE lang = 'zh-CN';
+
+UPDATE t_subscription_plans
+SET price_display = CASE plan_id
+ WHEN 'week' THEN '$1/week'
+ WHEN 'month' THEN '$3/month'
+ WHEN 'quarter' THEN '$8/quarter'
+ WHEN 'year' THEN '$12/year'
+ ELSE price_display
+ END,
+ price_cents = CASE plan_id
+ WHEN 'week' THEN 100
+ WHEN 'month' THEN 300
+ WHEN 'quarter' THEN 800
+ WHEN 'year' THEN 1200
+ ELSE price_cents
+ END,
+ currency = 'USD',
+ product_id = CASE plan_id
+ WHEN 'week' THEN 'health.wyrs365.com.vip.week'
+ WHEN 'month' THEN 'health.wyrs365.com.vip.month'
+ WHEN 'quarter' THEN 'health.wyrs365.com.vip.quarter'
+ WHEN 'year' THEN 'health.wyrs365.com.vip.year'
+ ELSE product_id
+ END,
+ updated_at = CURRENT_TIMESTAMP(3)
+WHERE lang = 'en';
+
+UPDATE t_subscription_plans
+SET price_display = CASE plan_id
+ WHEN 'week' THEN '$1/週'
+ WHEN 'month' THEN '$3/月'
+ WHEN 'quarter' THEN '$8/季'
+ WHEN 'year' THEN '$12/年'
+ ELSE price_display
+ END,
+ price_cents = CASE plan_id
+ WHEN 'week' THEN 100
+ WHEN 'month' THEN 300
+ WHEN 'quarter' THEN 800
+ WHEN 'year' THEN 1200
+ ELSE price_cents
+ END,
+ currency = 'USD',
+ product_id = CASE plan_id
+ WHEN 'week' THEN 'health.wyrs365.com.vip.week'
+ WHEN 'month' THEN 'health.wyrs365.com.vip.month'
+ WHEN 'quarter' THEN 'health.wyrs365.com.vip.quarter'
+ WHEN 'year' THEN 'health.wyrs365.com.vip.year'
+ ELSE product_id
+ END,
+ updated_at = CURRENT_TIMESTAMP(3)
+WHERE lang = 'zh-TW';
+
+-- 若库中尚无种子,补齐三语默认档(幂等)
+INSERT INTO t_subscription_plans
+(plan_id, lang, title, subtitle, price_display, price_cents, currency, period_days, badge, sort_order, enabled, product_id, updated_at)
+VALUES
+('week', 'zh-CN', '周卡', '灵活体验,随时可停', '$1/周', 100, 'USD', 7, '', 1, 1, 'health.wyrs365.com.vip.week', CURRENT_TIMESTAMP(3)),
+('month', 'zh-CN', '月卡', '最受欢迎', '$3/月', 300, 'USD', 30, '热门', 2, 1, 'health.wyrs365.com.vip.month', CURRENT_TIMESTAMP(3)),
+('quarter', 'zh-CN', '季卡', '平均每月更省', '$8/季', 800, 'USD', 90, '', 3, 1, 'health.wyrs365.com.vip.quarter', CURRENT_TIMESTAMP(3)),
+('year', 'zh-CN', '年卡', '全年畅听会员曲库', '$12/年', 1200, 'USD', 365, '超值', 4, 1, 'health.wyrs365.com.vip.year', CURRENT_TIMESTAMP(3)),
+('week', 'en', 'Weekly', 'Flexible, cancel anytime', '$1/week', 100, 'USD', 7, '', 1, 1, 'health.wyrs365.com.vip.week', CURRENT_TIMESTAMP(3)),
+('month', 'en', 'Monthly', 'Most popular', '$3/month', 300, 'USD', 30, 'Popular', 2, 1, 'health.wyrs365.com.vip.month', CURRENT_TIMESTAMP(3)),
+('quarter', 'en', 'Quarterly', 'Better monthly value', '$8/quarter', 800, 'USD', 90, '', 3, 1, 'health.wyrs365.com.vip.quarter', CURRENT_TIMESTAMP(3)),
+('year', 'en', 'Yearly', 'Best value all year', '$12/year', 1200, 'USD', 365, 'Best', 4, 1, 'health.wyrs365.com.vip.year', CURRENT_TIMESTAMP(3)),
+('week', 'zh-TW', '週卡', '靈活體驗,隨時可停', '$1/週', 100, 'USD', 7, '', 1, 1, 'health.wyrs365.com.vip.week', CURRENT_TIMESTAMP(3)),
+('month', 'zh-TW', '月卡', '最受歡迎', '$3/月', 300, 'USD', 30, '熱門', 2, 1, 'health.wyrs365.com.vip.month', CURRENT_TIMESTAMP(3)),
+('quarter', 'zh-TW', '季卡', '平均每月更省', '$8/季', 800, 'USD', 90, '', 3, 1, 'health.wyrs365.com.vip.quarter', CURRENT_TIMESTAMP(3)),
+('year', 'zh-TW', '年卡', '全年暢聽會員曲庫', '$12/年', 1200, 'USD', 365, '超值', 4, 1, 'health.wyrs365.com.vip.year', CURRENT_TIMESTAMP(3))
+ON DUPLICATE KEY UPDATE
+ price_display = VALUES(price_display),
+ price_cents = VALUES(price_cents),
+ currency = VALUES(currency),
+ product_id = VALUES(product_id),
+ updated_at = VALUES(updated_at);