Files
Genarrative/docs/technical/MY_TAB_ACCOUNT_RECHARGE_IMPLEMENTATION_2026-04-25.md

9.9 KiB
Raw Blame History

“我的”账户充值弹窗落地设计

日期:2026-04-25

1. 范围

本轮在“我的”页面的“会员充值”入口落地账户充值弹窗,包含两个页签:

  1. 泥点充值
  2. 会员卡充值

前端只负责展示与发起购买,套餐、价格、赠送规则、会员权益、生效时间、钱包余额与交易流水统一由 server-rs 后端返回。普通 H5 / 本地联调继续使用 mock 渠道:创建订单后立即写入余额或会员状态,并返回最新账户中心快照。微信小程序 web-view 使用 wechat_mp 渠道:创建订单时只写入 pending 订单并返回小程序 wx.requestPayment 参数,真实到账以后端微信支付通知为准。

2. 产品规则

2.1 泥点充值套餐

productId 泥点 金额分 徽标 说明
points_60 60 600 首充双倍 首充送60泥点
points_180 180 1800 首充双倍 首充送180泥点
points_300 300 3000 首充双倍 首充送300泥点
points_680 680 6800 首充双倍 首充送680泥点
points_1280 1280 12800 首充双倍 首充送1280泥点
points_3280 3280 32800 首充双倍 首充送3280泥点

泥点充值固定为 ¥6 / ¥18 / ¥30 / ¥68 / ¥128 / ¥328 六个档位。全部档位参与首充双倍:用户历史上没有 points_recharge 流水时,本次购买到账泥点为基础泥点与等额赠送泥点之和;已有充值流水后只到账基础泥点。实际到账泥点写入交易流水,余额以 SpacetimeDB projection 为准。

2.2 会员卡套餐

productId 类型 天数 金额分 权益
member_month 月卡 30 2800 免泥点回合数100每日签到加成0%
member_season 季卡 90 7800 免泥点回合数100每日签到加成100%
member_year 年卡 365 24800 免泥点回合数100每日签到加成210%

购买会员时,如果当前会员仍有效,则从当前到期时间顺延;如果已过期或从未购买,则从当前服务端时间开始计算。状态只区分 普通 与已生效会员,前端不自行推断。

3. 后端接口

3.1 GET /api/profile/recharge-center

需要 Bearer JWT。返回

  1. 当前泥点余额、会员状态、到期时间
  2. 泥点套餐与会员套餐
  3. 会员权益表
  4. 最近订单摘要

兼容路径:GET /api/runtime/profile/recharge-center

3.2 POST /api/profile/recharge/orders

需要 Bearer JWT。请求

{
  "productId": "points_300",
  "paymentChannel": "mock"
}

行为:

  1. 校验 productId
  2. paymentChannel = "mock" 时后端创建已支付订单
  3. paymentChannel = "wechat_mp" 时后端创建待支付订单,并调用微信支付 JSAPI 下单生成小程序支付参数;本地 orderId 会作为微信 out_trade_no 传递,格式固定为 rcg 前缀 + 小写字母数字,长度在 6-32 字符内,满足微信支付 JSAPI 下单文档对商户订单号的限制。商品描述限制为 127 字符内,回调地址限制为 HTTPS、255 字符内且不携带 query/fragment。
    • JSAPI 下单请求必须显式携带 Accept: application/jsonContent-Type: application/jsonUser-Agent: Genarrative-WechatPay/1.0;微信侧会把缺少 User-Agent 的请求返回为“Http头缺少Accept或User-Agent”。
  4. mock 泥点套餐立即写入钱包余额与流水mock 会员套餐立即写入会员状态
  5. wechat_mp 订单不提前发泥点或会员,只返回待支付订单、账户中心快照与 wechatMiniProgramPayParams

兼容路径:POST /api/runtime/profile/recharge/orders

响应里的 wechatMiniProgramPayParams 只在微信小程序支付渠道返回,字段直接对应 wx.requestPayment

{
  "wechatMiniProgramPayParams": {
    "timeStamp": "1777110165",
    "nonceStr": "nonce",
    "package": "prepay_id=wx201410272009395522657a690389285100",
    "signType": "RSA",
    "paySign": "..."
  }
}

3.3 POST /api/profile/recharge/orders/{order_id}/wechat/confirm

需要 Bearer JWT。该接口用于小程序支付页返回 web-view 后的主动查单确认,不替代微信支付通知:

  1. 后端读取本地 profile_recharge_order 并校验订单归属、支付渠道和当前状态。
  2. 若订单已是 paid,直接返回订单与账户中心快照。
  3. 若订单仍是 pending,后端调用微信支付按商户订单号查单接口。
  4. 只有微信查单返回 trade_state = "SUCCESS" 时,才调用统一入账 procedure 把订单改为 paid 并写入钱包流水或会员状态。
  5. 如果微信查单仍不是 SUCCESS,接口返回当前 pending 订单与账户中心快照;前端只在全局支付结果模态显示“支付已提交”,不提前发放泥点或会员。

响应结构:

{
  "order": {
    "orderId": "rcg...",
    "status": "paid"
  },
  "center": {
    "walletBalance": 120
  }
}

3.4 POST /api/profile/recharge/wechat/notify

微信支付通知地址,无需 Bearer JWT。行为

  1. 真实渠道使用微信支付平台公钥和 Wechatpay-* 请求头验签;验签必须使用原始 HTTP body bytes 构造 timestamp\nnonce\nbody\n,不能先把 body 转成字符串再重建。
  2. 使用 WECHAT_PAY_API_V3_KEY 解密通知 resource
  3. 仅当 trade_state = "SUCCESS" 时确认订单支付。
  4. 使用微信通知里的 out_trade_no 查本地 profile_recharge_order.order_id,把订单从 pending 改为 paid
  5. 将微信平台订单号写入 provider_transaction_id,用于对账、查单、退款和客服排障。
  6. 在同一 SpacetimeDB procedure 内写入钱包流水或会员到期时间,确保重复通知幂等。
  7. 验签、解密和业务确认通过后返回 HTTP 204 No Content;不要返回 V2 XML。
  8. 微信支付公钥模式下,真实请求会携带 Wechatpay-Serial: PUB_KEY_ID_...,通知验签必须要求回调头 Wechatpay-SerialWECHAT_PAY_PLATFORM_SERIAL_NO 对应;若不匹配应返回 401 并在日志里记录 reason。

关键环境变量:

变量 说明
WECHAT_PAY_ENABLED 是否启用微信支付客户端
WECHAT_PAY_PROVIDER mockreal
WECHAT_PAY_MCH_ID 微信支付商户号
WECHAT_PAY_MERCHANT_SERIAL_NO 商户 API 证书序列号,用于请求微信支付签名头
WECHAT_PAY_PRIVATE_KEY_PEM / WECHAT_PAY_PRIVATE_KEY_PATH 商户 API 私钥
WECHAT_PAY_PLATFORM_PUBLIC_KEY_PEM / WECHAT_PAY_PLATFORM_PUBLIC_KEY_PATH 微信支付平台公钥或平台证书公钥,用于回调验签
WECHAT_PAY_PLATFORM_SERIAL_NO 微信支付通知头里的平台证书/公钥序列号
WECHAT_PAY_API_V3_KEY 32 字节 API v3 密钥,用于解密通知资源
WECHAT_PAY_NOTIFY_URL 公网 HTTPS 通知地址,通常为 /api/profile/recharge/wechat/notify

4. 前端交互

  1. “我的”页会员充值按钮打开独立弹窗,不在当前面板下方展开。
  2. 弹窗顶部标题为 账户充值,右上角关闭。
  3. 默认打开 泥点充值,可切换到 会员卡充值
  4. 点击套餐后调用下单接口,按钮进入处理中状态;小程序环境走 native 支付页拉起 wx.requestPayment,支付页返回后刷新 profileDashboard
    • 小程序 web-view 内的 H5 只负责加载微信 JS-SDK 并通过 wx.miniProgram.navigateTo 跳转到 /pages/wechat-pay/index;实际支付必须在小程序 native 页调用 wx.requestPayment,不要切换为 H5 支付产品。
    • native 支付页通过 wx_pay_result=<requestId>:success|cancel|fail 回填 web-viewH5 在 hashchangefocuspageshowvisibilitychange 中都会尝试消费该结果,避免小程序返回 web-view 时没有触发单一事件导致状态不刷新。
    • success 只表示微信客户端支付流程返回成功,前端随后调用 POST /api/profile/recharge/orders/{order_id}/wechat/confirm 由服务端查单确认;只有通知或服务端查单确认为 SUCCESS 才入账。
    • cancelfail 只复位按钮、刷新账户中心并通过全局支付结果模态展示,不调用入账逻辑。
  5. 支付结果使用页面级全局模态展示,不写回商品卡片或账户充值弹窗内部;充值弹窗只负责套餐选择、加载失败和下单失败。
  6. 弹窗内不写大段说明文案,只保留必要金额、泥点、会员权益和操作状态。
  7. 会员卡充值区以套餐卡片优先展示周期、价格和处理状态;移动端单列,桌面端三列,权益表允许横向滚动,避免小屏挤压。

5. 验收

  1. 普通用户打开弹窗能看到泥点与会员套餐。
  2. 泥点购买后余额增加,流水来源为 points_recharge
  3. 首充赠送只在首次泥点充值时生效。
  4. 会员购买后会员状态与到期时间立即更新。
  5. 移动端弹窗单列可滚动,桌面端接近参考图卡片网格。