Răsfoiți Sursa

docs: 会员支付/升级页面设计(方案B:upgrade→pay→result 三段式 + 原价配置)

Xiaogang Liao 1 lună în urmă
părinte
comite
e183e2315d

+ 241 - 0
docs/superpowers/specs/2026-08-09-membership-pay-upgrade-design.md

@@ -0,0 +1,241 @@
+# 会员支付/升级页面设计(方案 B)
+
+日期:2026-08-09
+状态:已获用户批准(方向 B + 优惠券保留 + 原价定义确认)
+
+## 1. 背景与目标
+
+现有会员系统后端链路完整(等级 FREE/FAMILY/PREMIUM、下单、微信虚拟支付、拆单分期、优惠券、开通赠券、升级记录、权益列表),但前端把「选套餐 + 选优惠券 + 支付 + 结果」全部塞进 `upgrade.vue` 一个页面,支付确认环节不清晰、拆单分期体验差。
+
+目标:按方案 B 将支付链路拆分为「套餐选择 → 订单确认 → 支付结果」三段式,新增 `pay.vue`、`result.vue` 两个页面,改造 `upgrade.vue`,后端零改动(除新增原价配置)。
+
+### 已确认需求
+
+1. 方案 B 页面结构(index → upgrade → pay → result)
+2. 优惠券功能保留并修复(`createOrder` 透传 `couponId`)
+3. 会员价格支持「原价」定义(sys_config 驱动,仅展示用途)
+
+## 2. 页面架构与路由
+
+```
+pages/membership/
+├── index.vue    会员中心(已有,小幅优化)
+├── upgrade.vue  套餐选择(改造:移除支付逻辑,专注选套餐)
+├── pay.vue      订单确认(新增 ⭐)
+├── result.vue   支付结果(新增 ⭐)
+└── benefits.vue 我的权益(已有,保留不动)
+```
+
+**路由跳转:**
+
+```
+index ──(开通/续费)──→ upgrade ──(下一步)──→ pay?levelCode=FAMILY&period=yearly
+                                                  │
+                                    (微信虚拟支付 或 模拟支付)
+                                                  │
+                                             result?orderNo=xxx&status=success/fail
+```
+
+**pages.json 注册**(`pages/membership` 分包内追加 2 页):
+
+| path | navigationBarTitleText |
+|------|------------------------|
+| `pay` | 确认订单 |
+| `result` | 支付结果 |
+
+## 3. 后端变更(最小,仅原价配置)
+
+### 3.1 sys_config 新增 4 个键(单位:分)
+
+| 配置键 | 默认值 | 含义 |
+|--------|--------|------|
+| `member_fee_family_original_yearly` | `199900` | 家庭会员年费原价(分)¥1999 |
+| `member_fee_family_original` | `199900` | 家庭会员原价兜底(分) |
+| `member_fee_premium_original` | `1599900` | 高级会员年费原价(分)¥15999 |
+| `member_fee_premium_original_monthly` | `159900` | 高级会员月费原价(分)¥1599 |
+
+- 在 `DatabaseInitializer.java` 的 SysConfig 种子区追加 `insertSysConfigSeed`(幂等)
+- 原价仅作展示,不参与任何折扣/优惠券/拆单计算
+
+### 3.2 `/api/membership/plans` 接口增强
+
+`MembershipController.getPricingPlans()` 每个 plan 增加原价字段(无原价返回 null):
+
+```json
+{
+  "levelCode": "FAMILY",
+  "levelName": "家庭会员",
+  "levelDesc": "...",
+  "recommended": true,
+  "yearly": 131400,
+  "originalYearly": 199900,
+  "monthly": null,
+  "originalMonthly": null,
+  "quarterly": null,
+  "originalQuarterly": null
+}
+```
+
+- FAMILY:`originalYearly` 读 `member_fee_family_original_yearly`(兜底 `member_fee_family_original`)
+- PREMIUM:`originalYearly` 读 `member_fee_premium_original`,`originalMonthly` 读 `member_fee_premium_original_monthly`
+- 其他等级:读 `membership_levels` 表,无原价字段 → 返回 null(不展示划线价)
+
+## 4. 前端页面设计
+
+### 4.1 upgrade.vue(套餐选择页 · 改造)
+
+**保留**:
+- 状态卡(会员等级 / 到期时间 / 剩余天数)
+- 等级切换 tab(FAMILY / PREMIUM,非免费用户默认选中当前等级)
+- 档位选择(月/季/年,FAMILY 仅年付;PREMIUM 月付+年付)
+- 价格展示(含划线原价,见 4.4)
+- 权益对比表、开通赠券规则、升级记录
+
+**移除**:优惠券选择、支付调用、支付结果轮询(下沉到 pay.vue)
+
+**改动**:
+- 底部主按钮改「下一步」→ `navigateTo /pages/membership/pay?levelCode=xxx&period=xxx`
+- 修复 bug:等级判断用 `m.memberLevel`(现代码用 `m.levelCode`,顶层无此字段,会员状态恒显免费)
+- 非免费用户按钮文案「续费」
+
+### 4.2 pay.vue(订单确认页 · 新增)⭐
+
+**布局(自上而下):**
+
+```
+┌─────────────────────────────┐
+│ 订单确认(导航栏)            │
+├─────────────────────────────┤
+│ 商品明细卡                    │
+│   家庭会员(FAMILY)· 年度    │
+│   原价    ¥1999.00(划线)    │
+│   现价    ¥1314.00           │
+│   优惠券  -¥50.00  [选择▾]   │
+│   ──────────────────────    │
+│   实付    ¥1264.00           │
+├─────────────────────────────┤
+│ 拆单提示卡(仅 totalPeriods>1)│
+│   💡 本订单将分 N 期支付       │
+│   每期 ¥xxx,共 N 期          │
+├─────────────────────────────┤
+│ 底部固定:                    │
+│   [立即支付 ¥1264.00]        │
+└─────────────────────────────┘
+```
+
+**核心逻辑:**
+1. `onLoad` 读 `levelCode`、`period` → `getMembershipPlans()` 取价格(含原价)
+2. `getCouponList()` 加载可用优惠券 → 底部半屏 Picker(复用现有样式)
+3. **api.js 修复**:`createOrder(levelCode, paymentType, period, couponId)` 增加 couponId 透传
+4. 点击支付 → `createOrder(levelCode, 'pay', period, couponId)` → 三种分支:
+   - **有 `orderInfo+sign`** → `requestVirtualPayment` 调起微信支付
+   - **testMode 返回纯 order** → 直接走成功轮询(模拟支付)
+   - **拆单(totalPeriods>1)** → 支付第 1 期 → `nextOrder` 取下一期 → 逐期支付,UI 显示「第 N 期 / 共 M 期」进度
+5. 支付完成 → `navigateTo result?orderNo=xxx&status=success`;取消/失败 → `status=fail`
+
+**错误处理:**
+
+| 错误 | 处理 |
+|------|------|
+| 46001 | toast「该商品暂未开放购买」,返回 upgrade |
+| -15007 / -15005 | 登录态过期,提示重新登录 |
+| -15010 | 商品未发布,联系客服 |
+| -2 | 用户取消支付 → result fail 态 |
+| 其他 | 显示 errMsg → result fail 态 |
+
+### 4.3 result.vue(支付结果页 · 新增)
+
+**成功态:**
+
+```
+   ✅ 开通成功
+   家庭会员 FAMILY
+   有效期至 2027-08-09
+   🎁 已赠 2 张优惠券(券名)
+   [查看我的权益]  [返回会员中心]
+```
+
+**失败态:**
+
+```
+   ❌ 支付未完成
+   原因提示(取消/失败/登录态过期)
+   [重新支付] [返回套餐选择]
+   (拆单未付清时显示:第 N 期未支付,[继续支付])
+```
+
+**逻辑:**
+- `onLoad` 读 `orderNo/status`
+- 成功态:轮询 `getMyMembership()` 确认 `membership.isActive`;拉取开通赠券规则(`getJoinCouponRules`)展示赠券
+- 失败态:拆单未付清时调 `nextOrder({orderNo})` 获取下一期支付参数,提供「继续支付」
+
+### 4.4 价格展示规范(划线原价)
+
+**展示规则:**
+- `originalPrice` 存在且 `> 现价` 时展示划线原价
+- 无原价或 `≤ 现价` 时不展示(向后兼容)
+- 优惠券在现价基础上抵扣:实付 = 现价 − 优惠券面额
+- 拆单分期按抵扣后金额判断(与后端 `createOrder` 的 `finalAmount % goodsPrice` 一致)
+
+**视觉:**
+```
+原价 ¥1999.00  (灰、删除线、22rpx)
+现价 ¥1314.00   (大号橙字 64rpx)
+[立省 ¥685]     (橙底白字小标签,可选)
+```
+
+### 4.5 视觉规范(延续现有品牌)
+
+| 元素 | 规范 |
+|------|------|
+| 主色 | `#F97316` 暖橙渐变(会员品牌色,沿用) |
+| PREMIUM 区分色 | `#8B5CF6` 紫(沿用) |
+| 卡片 | 白底圆角 20rpx + 橙系阴影(沿用) |
+| 状态卡 | 橙渐变头图(沿用) |
+| 按钮 | 全宽圆角 44rpx,主橙 / 续费蓝 `#0EA5E9` |
+| 小程序限制 | 禁 `?.`、禁 Grid、`:key` 用方法、日期用 `parseDate()`、禁止 `:key` 表达式 |
+
+## 5. 数据流总览
+
+```
+upgrade: 选等级+档位 → navigateTo pay(levelCode, period)
+pay:     plans取价 + couponList取券
+         → 用户选券 → createOrder(levelCode,'pay',period,couponId)
+         → orderInfo+sign ? requestVirtualPayment(逐期) : 模拟成功
+         → 全部付清/回调 → navigateTo result
+result:  轮询 getMyMembership 确认开通 → 展示成功/失败 + 赠券
+```
+
+## 6. 改动清单
+
+### 后端(cfc-backend)
+| 文件 | 改动 |
+|------|------|
+| `config/DatabaseInitializer.java` | SysConfig 种子区加 4 个原价键(insertSysConfigSeed 幂等) |
+| `controller/MembershipController.java` | `getPricingPlans()` 增加 originalYearly/originalMonthly/originalQuarterly 字段 |
+
+### 前端(cfc-frontend)
+| 文件 | 改动 |
+|------|------|
+| `utils/api.js` | `createOrder` 增加 couponId 透传参数 |
+| `pages/membership/upgrade.vue` | 移除支付/优惠券逻辑;按钮改「下一步」;修会员状态判断 bug;价格区展示划线原价 |
+| `pages/membership/pay.vue` | **新增**:订单确认 + 优惠券 Picker + 支付调用 + 拆单逐期 |
+| `pages/membership/result.vue` | **新增**:支付结果成功/失败态 + 赠券展示 + 续付入口 |
+| `pages.json` | `pages/membership` 分包注册 pay、result 两页 |
+
+## 7. 测试要点
+
+1. **testMode 全链路**:upgrade 选套餐 → pay 确认 → 模拟支付 → result 成功态(含赠券展示)
+2. **拆单支付**(1314元 → 按 goods_price 拆 N 期):逐期支付成功 → result 成功;中途取消 → result 失败态 + 续付入口
+3. **优惠券**:抵扣金额显示正确;下单请求携带 couponId;无可用券时 Picker 显示空态
+4. **原价展示**:FAMILY/PREMIUM 显示划线原价;无原价配置的等级不显示;原价 ≤ 现价时不显示
+5. **会员/非会员两态**:免费用户「立即开通」;会员「续费」,默认选中当前等级
+6. **错误分支**:46001、登录态过期、支付取消各走对应提示
+7. **回归**:`mvn clean compile` 通过;既有 benefits/index 页不受影响
+
+## 8. 范围外(YAGNI)
+
+- 不新增支付方式(仅微信虚拟支付 + testMode 模拟)
+- 不做自动续费 UI(后端 autoRenew 字段已有但本期不接)
+- 不改 `membership_levels` 表结构(原价走 sys_config,避免迁移)
+- 不做后端价格校验变更(createOrder 原价无关)