Просмотр исходного кода

fix(membership): 权益对比取消等级折扣口径,付费档统一会员价

- getMembershipCompareData:「商品折扣」行改为「商品价格」——FREE=普通价格,
  BASIC/FAMILY/PREMIUM 统一=会员价(实际成交走 MemberPriceResolver + products.member_price,
  仅按 min_discount_level 判门槛,不存在按等级递增的百分比折扣)
- 删除随之失效的 formatDiscountBps(bps→「X折」展示)
- 迁移450:membership_benefits 的 FAMILY/PREMIUM「商品会员折扣 全场商品 8-9折/6-8折」
  对齐为「商品会员价 / 指定商品享会员价」(DML 幂等),schema.sql 种子同步
- POST /api/membership/discount 置 410 废弃(核对小程序/管理端零调用),
  MembershipController.applyDiscount、MembershipService(.applyMemberDiscount)、
  MembershipServiceInterface 同步标 @Deprecated
- 文档/测试同步:API_REFERENCE 4.50、PROJECT-OVERVIEW、membership-flow、e2e 冒烟断言 410
- 验证:cfc-backend `mvn -q -DskipTests compile` 通过
Agnes Agent 6 дней назад
Родитель
Сommit
787010381f

+ 28 - 2
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -3449,6 +3449,32 @@ insertSysConfigSeed("gift_membership_pay_price", "198000", "送会员商品达
         // 以容纳注册中心子维度 code(如 mind.selfConceptOverall=23 字符 > 原 VARCHAR(16)),
         // 指标可归属/挂接到任意维度节点(红线1:不动 id 主键与 5 张外键表)
         migrateIndicatorDimensionCodeWidth();
+        // 迁移450: 会员权益文案与成交口径对齐——付费档统一享「会员价」,无按等级递增的商品折扣
+        alignMembershipBenefitMemberPriceCopy();
+    }
+
+    /**
+     * 迁移450: 会员权益文案与实际成交口径对齐(DML,幂等)。
+     *
+     * <p>FAMILY/PREMIUM 的「商品会员折扣」原描述为「全场商品 8-9 折优惠 / 6-8 折优惠」,
+     * 但实际结算由 {@code MemberPriceResolver} 取 {@code products.member_price},仅按
+     * {@code products.min_discount_level} 判定是否达门槛,不存在按会员等级递增的百分比折扣。
+     * 此处把权益名与描述统一改为「商品会员价 / 指定商品享会员价」,避免与真实成交价不符。</p>
+     */
+    private void alignMembershipBenefitMemberPriceCopy() {
+        try {
+            int familyRows = jdbcTemplate.update(
+                    "UPDATE membership_benefits SET benefit_name = ?, benefit_desc = ? "
+                            + "WHERE level_code = 'FAMILY' AND benefit_code = 'PRODUCT_DISCOUNT'",
+                    "商品会员价", "指定商品享会员价");
+            int premiumRows = jdbcTemplate.update(
+                    "UPDATE membership_benefits SET benefit_name = ?, benefit_desc = ? "
+                            + "WHERE level_code = 'PREMIUM' AND benefit_code = 'PRODUCT_DISCOUNT'",
+                    "商品会员价", "指定商品享会员价");
+            log.info("迁移450: 已对齐 FAMILY/PREMIUM「商品会员价」权益文案({} / {} 行)", familyRows, premiumRows);
+        } catch (Exception e) {
+            log.warn("迁移450: 对齐会员权益文案失败(幂等容忍): {}", e.getMessage());
+        }
     }
 
     /**
@@ -8983,14 +9009,14 @@ sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min,
                     "('FAMILY', 'HEALTH_PLAN', '月度健康计划', '个性化健康改善方案', '📋', '#FEF3C7', 3), " +
                     "('FAMILY', 'REPORT_READING', '报告智能解读', '体检/菌群/测评报告 AI 解读', '📊', '#FCE7F3', 4), " +
                     "('FAMILY', 'MANAGER_SERVICE', '专属健康教练服务', '认证健康教练月度随访', '👨‍🏫', '#FFEDD5', 5), " +
-                    "('FAMILY', 'PRODUCT_DISCOUNT', '商品会员折扣', '全场商品 8-9 折优惠', '🛒', '#E0E7FF', 6), " +
+                    "('FAMILY', 'PRODUCT_DISCOUNT', '商品会员价', '指定商品享会员价', '🛒', '#E0E7FF', 6), " +
                     "('FAMILY', 'COUPON_ACCESS', '优惠券使用权限', '可使用平台优惠券享更多优惠', '🎫', '#FEE2E2', 7), " +
                     "('PREMIUM', 'AI_CHAT', 'AI 健康顾问', '7×24 小时不限次数问答', '🤖', '#DBEAFE', 1), " +
                     "('PREMIUM', 'FAMILY_MEMBERS', '家庭档案管理', '管理最多 20 位家庭成员', '👨‍👩‍👧‍👦', '#D1FAE5', 2), " +
                     "('PREMIUM', 'HEALTH_PLAN', '月度健康计划', '个性化健康改善方案 + 定制营养建议', '📋', '#FEF3C7', 3), " +
                     "('PREMIUM', 'REPORT_READING', '报告智能解读', '体检/菌群/测评报告 AI 深度解读', '📊', '#FCE7F3', 4), " +
                     "('PREMIUM', 'MANAGER_SERVICE', '专属健康教练服务', '每周 1 次电话随访 + 在线答疑', '👨‍🏫', '#FFEDD5', 5), " +
-                    "('PREMIUM', 'PRODUCT_DISCOUNT', '商品会员折扣', '全场商品 6-8 折优惠', '🛒', '#E0E7FF', 6), " +
+                    "('PREMIUM', 'PRODUCT_DISCOUNT', '商品会员价', '指定商品享会员价', '🛒', '#E0E7FF', 6), " +
                     "('PREMIUM', 'COUPON_ACCESS', '优惠券使用权限', '可使用平台优惠券 + 会员专属券', '🎫', '#FEE2E2', 7), " +
                     "('PREMIUM', 'PRIORITY_SUPPORT', '优先客服', '专属客服通道,快速响应', '🎧', '#EDE9FE', 8), " +
                     "('PREMIUM', 'TEACHER_CONSULT', '规划师一对一咨询', '每月 1 次成长规划师专业咨询', '👩‍🏫', '#FBCFE8', 9)");

+ 8 - 14
cfc-backend/src/main/java/com/etotem/cfc/controller/MembershipController.java

@@ -410,24 +410,18 @@ public class MembershipController {
     }
 
     /**
-     * 应用会员折扣
+     * 应用会员折扣(已废弃)。
+     *
+     * @deprecated 2026-11-06:商品/活动实际结算走 MemberPriceResolver
+     *     (products.member_price + min_discount_level 门槛),不再按会员等级做百分比折扣;
+     *     小程序/管理端均无调用方,按项目规范保留 410 提示,确认零调用后清理。
      */
-    @Operation(summary = "应用会员折扣")
+    @Deprecated
+    @Operation(summary = "应用会员折扣(已废弃,返回 410)")
     @PostMapping("/discount")
     public Result<Map<String, Object>> applyDiscount(@RequestBody Map<String, Object> params,
         @RequestAttribute("userId") Long userId) {
-        if (userId == null) {
-            return Result.error("用户未登录");
-        }
-
-        Integer originalPrice = Integer.valueOf(params.get("amount").toString());
-        Integer discountedPrice = membershipService.applyMemberDiscount(userId, originalPrice);
-
-        Map<String, Object> result = new HashMap<>();
-        result.put("originalPrice", originalPrice);
-        result.put("discountedPrice", discountedPrice);
-
-        return Result.success(result);
+        return Result.error(410, "该接口已废弃:会员商品价在下单时按会员价结算,无需再调用");
     }
 
     /**

+ 12 - 30
cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java

@@ -1638,8 +1638,13 @@ public class MembershipService implements MembershipServiceInterface {
     }
 
     /**
-     * 应用会员折扣,返回折后价格(分)
+     * 应用会员折扣,返回折后价格(分)。
+     *
+     * @deprecated 2026-11-06:商品/活动成交价统一走 {@code MemberPriceResolver}
+     *     (products.member_price + min_discount_level),不再按会员等级做百分比折扣。
+     *     本方法已无业务调用方(仅 /api/membership/discount 曾用,该接口已置 410),待清理。
      */
+    @Deprecated
     public Integer applyMemberDiscount(Long userId, Integer originalPrice) {
         if (originalPrice == null || originalPrice <= 0) {
             return 0;
@@ -1941,7 +1946,7 @@ public class MembershipService implements MembershipServiceInterface {
      * 生成会员权益对比表数据。
      * 列:FREE / BASIC / FAMILY / PREMIUM(可购档 + 免费;LIFETIME 不可购、PROVIDER 为服务商角色均不展示)。
      * 行全部来自事实源:
-     * - 商品折扣:sys_config member_discount_family/premium(bps)
+     * - 商品价格:付费档统一按商品 member_price(会员价)结算,不按等级叠加额外折扣
      * - 成员/任务上限:membership_levels.max_children / max_tasks_per_day
      * - 月度次数:BasicQuotaService.QUOTA_LIMITS
      * - 其余权益:membership_benefits 描述 + canUseFeature 功能开关
@@ -1962,13 +1967,11 @@ public class MembershipService implements MembershipServiceInterface {
         // 各等级权益描述(备用,后续可按 benefit_code 维度扩展对比行)
         List<Map<String, Object>> rows = new ArrayList<>();
 
-        // 1. 商品折扣:FREE=普通价格;BASIC=指定商品会员价;FAMILY=9折;PREMIUM=8折(bps 配置)
-        rows.add(compareRow("商品折扣", columns, code -> {
-            if ("FREE".equals(code)) return "普通价格";
-            if ("BASIC".equals(code)) return "指定商品会员价";
-            String cfgKey = "FAMILY".equals(code) ? "member_discount_family" : "member_discount_premium";
-            return formatDiscountBps(sysConfigService.getValue(cfgKey));
-        }));
+        // 1. 商品价格:FREE=普通价格;BASIC/FAMILY/PREMIUM 等付费档统一享会员价
+        //    (实际结算走 MemberPriceResolver + products.member_price,按 min_discount_level 门槛判定,
+        //      不存在按等级递增的额外折扣,故各付费档展示同一口径)
+        rows.add(compareRow("商品价格", columns,
+                code -> "FREE".equals(code) ? "普通价格" : "会员价"));
 
         // 2. 家庭成员上限
         rows.add(compareRow("家庭成员上限", columns, code -> {
@@ -2071,25 +2074,4 @@ public class MembershipService implements MembershipServiceInterface {
         }
         return row;
     }
-
-    /**
-     * 折扣 bps 转展示文本:9000 → "9折",8500 → "8.5折";非法值返回 "—"。
-     */
-    private String formatDiscountBps(String cfgValue) {
-        if (cfgValue == null || cfgValue.isEmpty()) {
-            return "—";
-        }
-        try {
-            int bps = Integer.parseInt(cfgValue);
-            if (bps <= 0 || bps >= 10000) {
-                return "—";
-            }
-            if (bps % 1000 == 0) {
-                return (bps / 1000) + "折";
-            }
-            return String.format(java.util.Locale.ROOT, "%.1f折", bps / 1000.0);
-        } catch (Exception e) {
-            return "—";
-        }
-    }
 }

+ 7 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/api/MembershipServiceInterface.java

@@ -27,7 +27,13 @@ public interface MembershipServiceInterface {
     /** 获取当前有效会员等级 */
     String getMemberLevel(Long userId);
 
-    /** 应用会员折扣,返回折后价格(分) */
+    /**
+     * 应用会员折扣,返回折后价格(分)。
+     *
+     * @deprecated 2026-11-06:成交价统一走 MemberPriceResolver(products.member_price + min_discount_level),
+     *     不再按会员等级做百分比折扣;对应接口 POST /api/membership/discount 已置 410。
+     */
+    @Deprecated
     Integer applyMemberDiscount(Long userId, Integer originalPrice);
 
     /**

+ 2 - 2
cfc-backend/src/main/resources/schema.sql

@@ -4139,14 +4139,14 @@ INSERT IGNORE INTO membership_benefits (level_code, benefit_code, benefit_name,
 ('FAMILY', 'HEALTH_PLAN', '月度健康计划', '个性化健康改善方案', '📋', '#FEF3C7', 3),
 ('FAMILY', 'REPORT_READING', '报告智能解读', '体检/菌群/测评报告 AI 解读', '📊', '#FCE7F3', 4),
 ('FAMILY', 'MANAGER_SERVICE', '专属健康教练服务', '认证健康教练月度随访', '👨‍🏫', '#FFEDD5', 5),
-('FAMILY', 'PRODUCT_DISCOUNT', '商品会员折扣', '全场商品 8-9 折优惠', '🛒', '#E0E7FF', 6),
+('FAMILY', 'PRODUCT_DISCOUNT', '商品会员价', '指定商品享会员价', '🛒', '#E0E7FF', 6),
 ('FAMILY', 'COUPON_ACCESS', '优惠券使用权限', '可使用平台优惠券享更多优惠', '🎫', '#FEE2E2', 7),
 ('PREMIUM', 'AI_CHAT', 'AI 健康顾问', '7×24 小时不限次数问答', '🤖', '#DBEAFE', 1),
 ('PREMIUM', 'FAMILY_MEMBERS', '家庭档案管理', '管理最多 20 位家庭成员', '👨‍👩‍👧‍👦', '#D1FAE5', 2),
 ('PREMIUM', 'HEALTH_PLAN', '月度健康计划', '个性化健康改善方案 + 定制营养建议', '📋', '#FEF3C7', 3),
 ('PREMIUM', 'REPORT_READING', '报告智能解读', '体检/菌群/测评报告 AI 深度解读', '📊', '#FCE7F3', 4),
 ('PREMIUM', 'MANAGER_SERVICE', '专属健康教练服务', '每周 1 次电话随访 + 在线答疑', '👨‍🏫', '#FFEDD5', 5),
-('PREMIUM', 'PRODUCT_DISCOUNT', '商品会员折扣', '全场商品 6-8 折优惠', '🛒', '#E0E7FF', 6),
+('PREMIUM', 'PRODUCT_DISCOUNT', '商品会员价', '指定商品享会员价', '🛒', '#E0E7FF', 6),
 ('PREMIUM', 'COUPON_ACCESS', '优惠券使用权限', '可使用平台优惠券 + 会员专属券', '🎫', '#FEE2E2', 7),
 ('PREMIUM', 'PRIORITY_SUPPORT', '优先客服', '专属客服通道,快速响应', '🎧', '#EDE9FE', 8),
 ('PREMIUM', 'TEACHER_CONSULT', '规划师一对一咨询', '每月 1 次成长规划师专业咨询', '👩‍🏫', '#FBCFE8', 9),

+ 3 - 5
docs/flows/membership-flow.md

@@ -103,10 +103,8 @@ flowchart TD
         A11 -->|"familyId, feature"| S10["⚙️ MembershipService.canUseFeature()"]:::service
         S10 -->|"按FREE/FAMILY/PREMIUM判定功能白名单"| D_USER
 
-        P3 -->|"应用会员折扣"| A12["🔗 POST /api/membership/discount"]:::api
-        A12 -->|"userId, amount"| S11["⚙️ MembershipService.applyMemberDiscount()"]:::service
-        S11 -->|"从sys_config读取折扣率"| D_CFG
-        S11 -->|"FREE无折扣, FAMILY/PREMIUM按折扣bps计算"| D_CFG
+        P3 -->|"会员折扣(已废弃 410)"| A12["🔗 POST /api/membership/discount ❌废弃"]:::api
+        A12 -.->|"已置 410"| S11["⚙️ MembershipService.applyMemberDiscount()(@Deprecated)"]:::service
     end
 
     %% ================================================================
@@ -214,7 +212,7 @@ flowchart TD
 | 端点 | 说明 | 端口 | 请求数据 | 响应数据 |
 |------|------|------|---------|---------|
 | `POST /api/membership/can-use` | 检查功能权限 | 📱小程序 | `{feature}` | `boolean` |
-| `POST /api/membership/discount` | 应用会员折扣 | 📱小程序 | `{amount}` | `{originalPrice, discountedPrice}` |
+| `POST /api/membership/discount` | ❌ 已废弃(410) | 原:应用会员折扣;商品成交价改由 `MemberPriceResolver` 在下单时按会员价结算 | 无 | 无 |
 
 ### 订阅管理
 

+ 1 - 1
docs/superpowers/PROJECT-OVERVIEW.md

@@ -227,7 +227,7 @@
 | AI健康管家+推荐引擎(P1) | `plans/2026-07-22-comprehensive-implementation.md`(Phase 2 Wave 3) |
 | **LIFETIME 终身会员(方案A:等级扩展+实时聚合+管理员审核)** | `specs/2026-08-07-xaf-partner-ppt-redesign-design.md` + `plans/2026-08-09-lifetime-membership.md` |
 | **AI健康教练人格分化与管家自助选择**(L2 自助绑管/换绑 + 浠宝/福宝人格话术 + 管理端分配管理与停接开关) ✅ 已完成 | `specs/2026-08-23-coach-butler-design.md` + `plans/2026-08-23-coach-butler.md` |
-| **BASIC 体验会员 + 权益对比表动态化 + PROVIDER 退出会员权益**(2026-10-18):迁移 381/351/321 统一 level_order 语义(越大越高,PROVIDER=1 与 FREE 同级)+ `getMemberLevel` 归一 PROVIDER→FREE + `canUseFeature` 移除全权限兜底 + 活动价判定去 PROVIDER + 新接口 `POST /api/membership/compare` 聚合各档实际权限(折扣 bps / max_children / max_tasks_per_day / BasicQuotaService 配额 / features 开关),upgrade.vue 与 index.vue 对比表改动态渲染 | `api/API_REFERENCE.md` 4.50 |
+| **BASIC 体验会员 + 权益对比表动态化 + PROVIDER 退出会员权益**(2026-10-18):迁移 381/351/321 统一 level_order 语义(越大越高,PROVIDER=1 与 FREE 同级)+ `getMemberLevel` 归一 PROVIDER→FREE + `canUseFeature` 移除全权限兜底 + 活动价判定去 PROVIDER + 新接口 `POST /api/membership/compare` 聚合各档实际权限(折扣 bps / max_children / max_tasks_per_day / BasicQuotaService 配额 / features 开关),upgrade.vue 与 index.vue 对比表改动态渲染;2026-11-06 对比表「商品折扣」行改「商品价格」(FREE=普通价格,其余付费档统一=会员价,去除按等级 9折/8折 表述) | `api/API_REFERENCE.md` 4.50 |
 | **CF 值体系统一 · 数据层与配置归口(F-1 + W0.1)**(2026-10-24):8 张新表(cf_balance / cf_balance_log / cf_earn_rule + 24 行种子 / cf_earn_rule_history / cf_direct_sales_tier / cf_indirect_sales_tier / cf_earn_daily_limit + service_config)+ coupon/user_coupon 补 family_id + 迁移 407-412 + `ConfigService.getConfig`(缓存 TTL 60s + `CfConfigCache.invalidate`) | `plans/2026-10-24-cf-value-unification-plan.md` + `api/API_REFERENCE.md` 六 |
 | **CF 值体系统一 · F-6 存量迁移**(2026-10-24):迁移417 将 `family_platform_balance`、`family_members.system_points`、`user_platform_balance` 一次性幂等迁入成员维度 `cf_balance`;三类 `migration_*` 流水守卫重复执行,旧表保留只读历史 | `plans/2026-10-24-cf-value-unification-plan.md` §六 + `api/API_REFERENCE.md` 6.4 |
 | **CF 值体系统一 · F-4 分佣收口 + F03a/F03b 发放接入**(2026-10-25):推荐人返佣收口为两级销售奖(F03a 按团队 8 层内滚动一年累计 P 点档位上限 70% / F03b 按自身滚动一年消费 P 点档位上限 20%),共同基数 = 本单消费 P 点;迁移418 补两级档位种子 + `dedup_ref_type` 对齐为 F03a/F03b,迁移420 新建 `ppoint_consumption_log`(滚动一年聚合数据源,唯一键 `order_type+order_id`;419 已被 `membership_levels.max_daily_analyze` 占用);发放统一走 F-2 `earnByRule` 只写 `cf_balance` + `cf_balance_log`;与 F-3a 共存(当前消费人 F02 + 推荐人 F03a/F03b 都走统一账本,`cf_transfer_record` 不再由分佣服务写入);`CommissionDistService.distribute` 写接口 410 Gone、`commission_dist_log` 转只读;`/api/commission/cf/rate` 扩展两级档位字段 | `plans/2026-10-24-cf-value-unification-plan.md` §四/§3.1/§4.2/§4.4 + `api/API_REFERENCE.md` 4.33 + 6.5 |

+ 3 - 3
docs/superpowers/api/API_REFERENCE.md

@@ -1867,7 +1867,7 @@ Web 管理端定义活动海报模板(迁移381 `activity_poster_templates` 
   "data": {
     "columns": ["FREE", "BASIC", "FAMILY", "PREMIUM"],
     "rows": [
-      { "name": "商品折扣", "free": "普通价格", "basic": "—", "family": "9折", "premium": "8折" },
+      { "name": "商品价格", "free": "普通价格", "basic": "会员价", "family": "会员价", "premium": "会员价" },
       { "name": "家庭成员上限", "free": "1人", "basic": "3人", "family": "6人", "premium": "10人" },
       { "name": "每日任务上限", "free": "5个/日", "basic": "3个/日", "family": "20个/日", "premium": "50个/日" }
     ]
@@ -1879,7 +1879,7 @@ Web 管理端定义活动海报模板(迁移381 `activity_poster_templates` 
 
 | 权益行 | 数据来源 |
 |--------|----------|
-| 商品折扣 | `sys_config.member_discount_family` / `member_discount_premium`(bps:9000→9折、8000→8折),BASIC 无折扣配置显示 `—` |
+| 商品价格 | FREE=`普通价格`,BASIC/FAMILY/PREMIUM 统一=`会员价`。实际结算由 `MemberPriceResolver` 取 `products.member_price`,仅按 `products.min_discount_level` 判定是否达门槛,**不存在按等级递增的额外折扣** |
 | 家庭成员上限 | `membership_levels.max_children`(0/空视为无该权益 → `—`) |
 | 每日任务上限 | `membership_levels.max_tasks_per_day` |
 | AI健康顾问 / 报告解读 / 测评预约 / 食材图片解析 | `BasicQuotaService.QUOTA_LIMITS` 月度配额常量(BASIC 限次)+ `canUseFeature` 判定(FAMILY/PREMIUM 不限) |
@@ -1891,7 +1891,7 @@ Web 管理端定义活动海报模板(迁移381 `activity_poster_templates` 
 > **列口径**:仅 `FREE/BASIC/FAMILY/PREMIUM` 四列。`LIFETIME` 为赠送/特殊发放、不可购买,不作为列展示;`PROVIDER` 是**服务商角色**,不属于会员权益体系(`level_order=1` 与 FREE 同级,`features` 为 NULL),不参与本表。
 > **`/api/membership/plans` 同步变更**:除 `LIFETIME` 外再过滤 `PROVIDER`,并附加 `maxChildren` / `maxTasksPerDay` 两个字段,前端档位卡片可直接引用。
 
-*文档更新:2026-10-18(会员权益对比表动态化 + PROVIDER 退出会员权益体系)*
+*文档更新:2026-10-18(会员权益对比表动态化 + PROVIDER 退出会员权益体系);2026-11-06(「商品折扣」行改为「商品价格」:FREE=普通价格,其余付费档统一=会员价,去除按等级 9折/8折 的表述)*
 
 ---
 

+ 2 - 2
tests/e2e/flow-l1-tests.js

@@ -1234,9 +1234,9 @@ async function testMembershipFlow() {
   r = await apiCall('POST', '/api/membership/can-use', { feature: 'advance_analysis' }, t);
   record(F, 'parent', '功能权限', r.status === 200 && r.body && r.body.code === 200, r.body ? 'can-use OK' : r.raw);
 
-  // 会员折扣(需要 amount 参数)
+  // 会员折扣(已废弃 2026-11-06:会员商品价在下单时按会员价结算,接口返回 410)
   r = await apiCall('POST', '/api/membership/discount', { amount: 1000 }, t);
-  record(F, 'parent', '会员折扣', r.status === 200 && r.body && r.body.code === 200, r.body ? 'discount OK' : r.raw);
+  record(F, 'parent', '会员折扣(已废弃)', r.status === 200 && r.body && r.body.code === 410, r.body ? 'discount deprecated OK' : r.raw);
 
   // 订阅状态(需要familyId)
   r = await apiCall('POST', '/api/subscription/status?familyId=2001', {}, t);