2026-08-09-membership-pay-upgrade-design.md 10 KB

会员支付/升级页面设计(方案 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):

{
  "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 原价无关)