# 会员支付/升级页面设计(方案 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 原价无关)