Files
slogan/app/docs/superpowers/specs/2026-07-31-commerce-monetization-design.md
T
admin b53c57e02d Add 'app/' from commit 'a11a66886941bba128d89c1124115e1b1c87a128'
git-subtree-dir: app
git-subtree-mainline: 6ebd902c6b
git-subtree-split: a11a668869
2026-08-04 14:59:55 +08:00

12 KiB
Raw Blame History

商业化四支柱设计(客户端)· slogan-app

目标: 以「个人形象设计」为主流程,把四支柱收入入口从「方案/单品」里长出来:VIP 会员充值、穿山甲广告激励、线下门店引流(美团联盟)、线上商品(京东/淘宝 CPS)。不做泛化场景广场。 核心原则: 客户端零硬编码业务配置;所有商业化入口以「接口可用」为开关 —— 后端未配置 key 时接口报错/返回空 → App 自动隐藏对应入口,主流程(生成方案 → 查看)不受影响。

1. 信息架构改造

改造前:/commercial 孤立 tab(会员占位卡 + 门店列表)
改造后:
  ├─ /commercial 会员中心(P0)        会员状态卡 · 套餐 · 广告激励 · 合作门店 · 最近优惠
  ├─ /plan-viewer 方案页(P0)          发型卡「做同款发型」· 穿衣清单「买同款/到店试穿」· 场合「延伸优惠」
  ├─ /wardrobe 衣橱(P1               长按「找升级款」
  └─ 全局(P2)                         开屏广告 / 信息流广告位(效果图页底部)

开关原则:每个商业化入口包一层 commercialGate(统一查询后端配置/捕获接口错误),未开通 → 按钮不渲染。启动时不做额外网络调用,入口可见性由首次打开该页面的接口结果决定(零新增请求)。

2. 支柱 A:会员中心(/commercial 重构)

2.1 页面结构

/ commercialConsumerStatefulWidget,保留现有门店列表与类型筛选)
  ├─ 会员状态卡:头像/会员名 · 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/recentP1,未开通则隐藏整卡)

2.2 支付时序(App 侧)

点击套餐 → POST /member/order/create {plan_id} → 返回 {order_no, pay_url}
       → 打开 PayWebViewPage(内嵌 webview_flutteriOS 用 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 抽象(供应商隔离)

// lib/core/ads/ads_provider.dart
abstract class AdsService {
  bool get enabled;          // appid 未配置 → falseApp 隐藏广告入口
  Future<bool> showRewarded(); // 激励视频,返回是否完整观看
}

// lib/core/ads/pangle_ads_service.dart —— 穿山甲实现(P1 接入 SDK,P0 仅接口 + mock
// P0MockAdsService —— 本地模拟 3 秒「播放」返回 true,保证主链路可开发可测
  • 初始化AdsConfigAppConfig 常量: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(门店券)
场合卡(P1detail 有 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/listP1
商品卡:封面图(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 常量,均为本地编译期配置)

// 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-listextra: CpsListArgs{title, source, categoryCode, city}
  /pay-webviewextra: 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) 后端 P1CPS 引擎)
P2 开屏/信息流广告位 + 穿山甲插件不可用时的原生 module 兜底 + 收益/返现展示 后端 P2

10. 合规(客户端侧)

  • iOS 充值:App Store 虚拟商品政策风险 → iOS 端隐藏会员套餐充值入口(Platform.isIOS 判断),保留广告激励 + 门店引流;「会员价」到店核销不受影响
  • 广告:隐私政策文案补充穿山甲 SDK 信息收集披露;提供「个性化广告关闭」设置项(穿山甲 SDK 提供,P1)
  • 跳转CPS 转链一律走联盟 deeplink,不在 App 内二次改链

11. 开发规范约束(沿用 slogan-app 现有规范)

  • Riverpod 3AsyncNotifier/Notifier/FutureProvider.familyprovider 文件放 lib/features/<feature>/<feature>_provider.dart
  • 新页面组件放 lib/features/commercial/(会员中心)、lib/features/cps/(商品列表);广告抽象放 lib/core/ads/
  • 图片 URL 一律 AppConfig.resolveUrl();价格字段分 → 元转换写死规则((fen / 100).toStringAsFixed(0)
  • 所有「隐藏入口」逻辑集中在入口组件内一行判定,不扩散到业务逻辑
  • 新页面必配 empty/error/loading 三态(复用 shared/widgets