From 854df551670b27d48e41116280f18a110b28fa67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Fri, 31 Jul 2026 13:11:53 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=95=86=E4=B8=9A=E5=8C=96=E5=9B=9B?= =?UTF-8?q?=E6=94=AF=E6=9F=B1=E5=AE=A2=E6=88=B7=E7=AB=AF=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=EF=BC=88=E4=BC=9A=E5=91=98=E4=B8=AD=E5=BF=83/CPS=20=E5=85=A5?= =?UTF-8?q?=E5=8F=A3/=E5=B9=BF=E5=91=8A=E6=BF=80=E5=8A=B1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2026-07-31-commerce-monetization-design.md | 204 ++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-31-commerce-monetization-design.md diff --git a/docs/superpowers/specs/2026-07-31-commerce-monetization-design.md b/docs/superpowers/specs/2026-07-31-commerce-monetization-design.md new file mode 100644 index 0000000..858e6e4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-31-commerce-monetization-design.md @@ -0,0 +1,204 @@ +# 商业化四支柱设计(客户端)· slogan-app + +> **目标:** 以「个人形象设计」为主流程,把四支柱收入入口从「方案/单品」里长出来:VIP 会员充值、穿山甲广告激励、线下门店引流(美团联盟)、线上商品(京东/淘宝 CPS)。不做泛化场景广场。 +> **核心原则:** 客户端零硬编码业务配置;所有商业化入口以「接口可用」为开关 —— 后端未配置 key 时接口报错/返回空 → App 自动隐藏对应入口,主流程(生成方案 → 查看)不受影响。 + +## 1. 信息架构改造 + +``` +改造前:/commercial 孤立 tab(会员占位卡 + 门店列表) +改造后: + ├─ /commercial 会员中心(P0) 会员状态卡 · 套餐 · 广告激励 · 合作门店 · 最近优惠 + ├─ /plan-viewer 方案页(P0) 发型卡「做同款发型」· 穿衣清单「买同款/到店试穿」· 场合「延伸优惠」 + ├─ /wardrobe 衣橱(P1) 长按「找升级款」 + └─ 全局(P2) 开屏广告 / 信息流广告位(效果图页底部) +``` + +**开关原则**:每个商业化入口包一层 `commercialGate`(统一查询后端配置/捕获接口错误),未开通 → 按钮不渲染。启动时不做额外网络调用,**入口可见性由首次打开该页面的接口结果决定**(零新增请求)。 + +## 2. 支柱 A:会员中心(/commercial 重构) + +### 2.1 页面结构 + +``` +/ commercial(ConsumerStatefulWidget,保留现有门店列表与类型筛选) + ├─ 会员状态卡:头像/会员名 · is_vip · expire_at(倒计时)· 权益 chips(无限效果图/优先AI/返现1.5x/门店折扣) + │ ├─ 未开通 →「立即开通」按钮 → 打开套餐 bottom sheet + │ └─ 已开通 →「会员码」按钮(store_discount 到店出示,P1) + ├─ 广告激励卡(非会员时显示): + │ ├─ 看视频 +1 效果图(每日 2 次,剩余次数显示) + │ └─ 看视频 1 天体验会员(每日 1 次) + ├─ 合作门店列表(现有 /partner-store/list 品牌合作门店保留,含「会员价」角标 P1; + │ 美团联盟到店券走 /cps/product/list,两条链路互不替换) + └─ 最近优惠(/cps/my/recent,P1,未开通则隐藏整卡) +``` + +### 2.2 支付时序(App 侧) + +``` +点击套餐 → POST /member/order/create {plan_id} → 返回 {order_no, pay_url} + → 打开 PayWebViewPage(内嵌 webview_flutter,iOS 用 WKWebView) + → WebView 加载 pay_url,监听 url 变化(payment 完成跳转) + → 同时 Timer 每 2s GET /member/order/status?order_no=... 轮询(上限 60s) + → status=paid → 关闭 WebView → 刷新 memberProvider → 成功 toast + → 超时 → 提示「支付结果确认中,请稍后在会员中心查看」(状态以服务端为准) +``` + +- 支付页依赖 `webview_flutter`(P0 引入,国内 App 常规做法);若平台编译受限,降级方案:`url_launcher` 唤起系统浏览器支付,返回 App 后仍走轮询(**P0 默认 url_launcher 方案,webview_flutter 留 P1**,减小依赖风险) + +### 2.3 状态与 Provider + +| Provider | 类型 | 数据 | 接口 | +|---|---|---|---| +| `memberProvider` | AsyncNotifier | isVip, expireAt, planName, benefits[] | GET /member/status | +| `memberPlanProvider` | FutureProvider | 套餐列表 | GET /member/plan/list | +| `orderCreateProvider` | Notifier.family(planId) | orderNo, payUrl | POST /member/order/create | +| `orderStatusProvider` | FutureProvider.family(orderNo) | status | GET /member/order/status | + +- `memberProvider` 缓存登录态期间;`refresh()` 在支付成功、广告领奖后调用 +- 权益 chips 文案从套餐 `features` JSON 解析 → 本地文案 map(`effect_unlimited→无限效果图` 等) + +## 3. 支柱 B:广告激励(lib/core/ads/ 新建) + +### 3.1 抽象(供应商隔离) + +```dart +// lib/core/ads/ads_provider.dart +abstract class AdsService { + bool get enabled; // appid 未配置 → false,App 隐藏广告入口 + Future showRewarded(); // 激励视频,返回是否完整观看 +} + +// lib/core/ads/pangle_ads_service.dart —— 穿山甲实现(P1 接入 SDK,P0 仅接口 + mock) +// P0:MockAdsService —— 本地模拟 3 秒「播放」返回 true,保证主链路可开发可测 +``` + +- **初始化**:`AdsConfig`(AppConfig 常量:pangleAppId 默认空)→ `adsServiceProvider` 单例 +- **降级**:appid 空 / SDK 初始化失败 → `enabled=false` → 会员中心激励卡、广告位全部不渲染 + +### 3.2 激励流程(服务端防刷,客户端只展示) + +``` +点击「看视频」→ adsService.showRewarded() + → 完整观看 → POST /ad/reward/claim {ad_type: effect_extra | vip_trial} + → 成功 → 展示奖励弹窗(+1 效果图 / 1 天体验会员)→ refresh 会员卡 + → 失败(限频/未配置)→ 隐藏式错误(后端返回「今日次数已用完」→ 入口变灰) +``` + +- 剩余次数展示:`/ad/reward/claim` 响应带回 `{reward: {ad_type, remaining_today}}`,App 本地缓存当日显示;或 P0 简单化 —— 仅在领取失败时提示次数用完 +- **效果图配额联动**:后端限额 = 基础 3 + 当日额外次数;App 端文案统一显示「今日剩余 X 次」(P1 后端在生成接口响应中带 `remaining` 字段,P0 保持现状提示) + +### 3.3 广告位(P2,本期只留占位) + +- 开屏广告:`/home` 进入时加载(P2) +- 信息流广告:`/plan-effect` 效果图 GridView 底部插一条(P2) + +## 4. 支柱 C/D:方案驱动 CPS 入口 + +### 4.1 方案页(/plan-viewer)三处入口 + +| 位置 | 按钮 | scene | 请求 | 跳转 | +|---|---|---|---|---| +| 发型卡尾部 | 「做同款发型」 | haircut | GET /cps/plan/recommend {plan_id, scene: haircut} | /cps-product-list(美团丽人/理发券) | +| 穿衣清单每项 trailing | 「买同款」 | item_buy | 京东搜索单品名 | /cps-product-list(电商商品) | +| 穿衣清单每项 trailing | 「到店试穿」 | item_upgrade | 美团服装类目 | /cps-product-list(门店券) | +| 场合卡(P1,detail 有 occasion 字段后) | 「延伸优惠」 | occasion | 映射表推荐 | /cps-product-list | + +- **可见性**:推荐接口返回空列表 / 接口报错(CPS 未开通)→ 该按钮隐藏;发型卡无发型名(默认发型)→ 隐藏「做同款」 +- **交互**:推荐返回列表 → push `/cps-product-list?source=&category_code=&city=`(带标题「做同款发型 · 丽人」);列表点击 → POST /cps/product/link {product_id, scene, plan_id} → 打开 deeplink +- **打开方式**:`url_launcher`(系统浏览器,携 pid 转链 URL;电商商品优先深链 App,P1 增强) + +### 4.2 衣橱升级款(/wardrobe 长按菜单加一项) + +``` +长按衣物卡 → 菜单:删除 / 找升级款 +「找升级款」→ GET /cps/wardrobe/upgrade {item_id} → /cps-product-list(标题「升级款 · 上衣」) +``` + +### 4.3 商品列表页 /cps-product-list + +``` +AppBar 标题(由入口传入)+ 分类 chips(/cps/category/list,P1) +商品卡:封面图(AppConfig.resolveUrl)· 名称 · 价格(分→元)· 店铺 · 佣金标 +上滑加载更多(page 分页,has_more 判定) +点击 → POST /cps/product/link → deeplink → url_launcher 打开 +``` + +### 4.4 Provider + +| Provider | 类型 | 接口 | +|---|---|---| +| `cpsRecommendProvider` | FutureProvider.family((planId, scene)) | GET /cps/plan/recommend | +| `cpsProductProvider` | AsyncNotifierProvider.family((source, categoryCode, city)) | GET /cps/product/list 分页 | +| `cpsUpgradeProvider` | FutureProvider.family(itemId) | GET /cps/wardrobe/upgrade | +| `cpsLinkAction` | Notifier | POST /cps/product/link | + +## 5. 会员权益在客户端的呈现 + +| 权益 | 客户端表现 | +|---|---| +| effect_unlimited | 效果图页配额提示隐藏(「每日限 3 次」文案在 isVip 时不显示) | +| ai_priority | 生成页状态文案「VIP 优先排队」(MVP 仅文案) | +| cps_commission_x15 | 商品卡佣金标签「返现加成 1.5x」(VIP 用户) | +| store_discount | 门店列表「会员价」角标 + 会员码页(P1) | + +## 6. 配置(AppConfig 常量,均为本地编译期配置) + +```dart +// lib/core/config/app_config.dart 追加 +static const String pangleAppId = ''; // 穿山甲 AppId,空 = 广告功能关闭 +static const bool cpsEnabled = true; // 兜底开关;最终以接口结果为准 +``` + +- 商业化开关**最终以接口为准**(后端 config 未配置 → 接口错误 → App 隐藏入口),本地常量只控制「广告 SDK 是否初始化」 + +## 7. API 映射与错误处理 + +| 后端接口 | 客户端方法 | 错误处理 | +|---|---|---| +| GET /member/status | memberProvider.build | 401 → 重登;其他 → 默认非会员 | +| GET /member/plan/list | memberPlanProvider | 错误 → 套餐区显示「暂未开通」 | +| POST /member/order/create | 下单动作 | 错误 → toast「支付未开通」 | +| GET /member/order/status | 轮询 | 404/错误 → 结束轮询提示稍后查看 | +| POST /member/order/notify | -(后端回调,客户端不参与) | - | +| POST /ad/reward/claim | 领奖动作 | 错误 → toast 后端 message(限频等) | +| GET /cps/plan/recommend | cpsRecommendProvider | 空/错误 → 入口隐藏 | +| GET /cps/product/list | cpsProductProvider | 空/错误 → 列表空态「暂未开放」 | +| POST /cps/product/link | cpsLinkAction | 错误 → toast「跳转失败」 | +| GET /cps/my/recent | recentProvider | 错误 → 整卡隐藏 | + +- 所有新接口走现有 `apiClientProvider`(Dio 封装,自动带 token),无需改动网络层 + +## 8. 路由与依赖变更 + +``` +/lib/main.dart 新增 route: + /cps-product-list(extra: CpsListArgs{title, source, categoryCode, city}) + /pay-webview(extra: PayWebviewArgs{orderNo, payUrl},P1 webview_flutter) +依赖(P0):url_launcher(打开 deeplink / 系统浏览器支付) +依赖(P1):webview_flutter(内嵌收银台)、穿山甲 SDK(pangle 插件) +``` + +- 穿山甲 SDK Flutter 插件社区维护不稳定 → **P1 先验证 iOS/Android 编译,若插件不可用则改为原生 module 接入(P2)**;P0 用 MockAdsService 保证业务链路先闭环 + +## 9. 分期与对齐 + +| 分期 | 客户端内容 | 依赖 | +|---|---|---| +| **P0** | /commercial 重构会员中心(状态/套餐/下单/轮询)+ MockAds + 广告激励入口 + 方案页「做同款发型/买同款/到店试穿」+ /cps-product-list + url_launcher + cps 入口隐藏逻辑 | 后端 P0(会员+广告接口) | +| **P1** | 场合卡「延伸优惠」+ 衣橱「找升级款」+ 最近优惠 + 会员码/门店折扣角标 + webview_flutter 内嵌收银台 + 穿山甲 SDK 接入(替换 Mock) | 后端 P1(CPS 引擎) | +| **P2** | 开屏/信息流广告位 + 穿山甲插件不可用时的原生 module 兜底 + 收益/返现展示 | 后端 P2 | + +## 10. 合规(客户端侧) + +- **iOS 充值**:App Store 虚拟商品政策风险 → iOS 端隐藏会员套餐充值入口(`Platform.isIOS` 判断),保留广告激励 + 门店引流;「会员价」到店核销不受影响 +- **广告**:隐私政策文案补充穿山甲 SDK 信息收集披露;提供「个性化广告关闭」设置项(穿山甲 SDK 提供,P1) +- **跳转**:CPS 转链一律走联盟 deeplink,不在 App 内二次改链 + +## 11. 开发规范约束(沿用 slogan-app 现有规范) + +- Riverpod 3:AsyncNotifier/Notifier/FutureProvider.family;provider 文件放 `lib/features//_provider.dart` +- 新页面组件放 `lib/features/commercial/`(会员中心)、`lib/features/cps/`(商品列表);广告抽象放 `lib/core/ads/` +- 图片 URL 一律 `AppConfig.resolveUrl()`;价格字段分 → 元转换写死规则(`(fen / 100).toStringAsFixed(0)`) +- 所有「隐藏入口」逻辑集中在入口组件内一行判定,不扩散到业务逻辑 +- 新页面必配 empty/error/loading 三态(复用 shared/widgets)