feat: gate recharge payment by login device
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
1. `泥点充值`
|
||||
2. `会员卡充值`
|
||||
|
||||
前端只负责展示与发起购买,套餐、价格、赠送规则、会员权益、生效时间、钱包余额与交易流水统一由 `server-rs` 后端返回。支付接入固定为普通商户直连模式:小程序 web-view 使用 `wechat_mp`,移动网页使用 `wechat_h5`,桌面网页使用 `wechat_native` 二维码。生产默认永远不走 `mock`;只有自动测试或显式测试配置手动传 `paymentChannel = "mock"` 时才允许 mock 即时入账。所有真实微信渠道的到账事实以后端微信支付通知和服务端查单为准。
|
||||
前端只负责展示与发起购买,套餐、价格、赠送规则、会员权益、生效时间、钱包余额与交易流水统一由 `server-rs` 后端返回。支付接入固定为普通商户直连模式:小程序 web-view 使用 `wechat_mp`,手机微信内网页使用 `wechat_h5`,桌面微信内网页使用 `wechat_native` 二维码。生产默认永远不走 `mock`;只有自动测试或显式测试配置手动传 `paymentChannel = "mock"` 时才允许 mock 即时入账。所有真实微信渠道的到账事实以后端微信支付通知和服务端查单为准。
|
||||
|
||||
## 2. 产品规则
|
||||
|
||||
@@ -64,13 +64,18 @@
|
||||
|
||||
1. 校验 `productId`
|
||||
2. 缺少 `paymentChannel` 或传入未知渠道时直接返回 `400`,不得再默认解释为 `mock`
|
||||
3. 生产/真实支付配置拒绝 `paymentChannel = "mock"`;mock 只服务自动测试或显式 mock 测试配置
|
||||
3. 后端根据 Bearer JWT 中的 `device` 快照拦截真实微信渠道,不能只信任前端隐藏入口或请求体传入的 `paymentChannel`
|
||||
- `wechat_mp` 只允许 `device.client_type = "mini_program"`
|
||||
- `wechat_h5` 只允许 `device.client_type = "wechat_h5"` 且 `device.client_platform` 为 `ios` 或 `android`
|
||||
- `wechat_native` 只允许 `device.client_type = "wechat_h5"` 且 `device.client_platform` 为 `windows`、`macos` 或 `linux`
|
||||
- 缺少设备快照、普通浏览器设备、手机微信手动传 Native、桌面微信手动传 H5 都返回 `403`
|
||||
4. 生产/真实支付配置拒绝 `paymentChannel = "mock"`;mock 只服务自动测试或显式 mock 测试配置
|
||||
- `wechat_mp` / `wechat_h5` / `wechat_native` 必须在 `WECHAT_PAY_ENABLED=true` 且 `WECHAT_PAY_PROVIDER=real` 时才允许下单;`WECHAT_PAY_PROVIDER=mock` 不能为真实微信渠道返回 mock 支付载荷。
|
||||
4. `paymentChannel = "wechat_mp"` 时后端创建待支付订单,并调用微信支付 JSAPI 下单生成小程序支付参数;本地 `orderId` 会作为微信 `out_trade_no` 传递,格式固定为 `rcg` 前缀 + 小写字母数字,长度在 6-32 字符内,满足微信支付 JSAPI 下单文档对商户订单号的限制。商品描述限制为 127 字符内,回调地址限制为 HTTPS、255 字符内且不携带 query/fragment。
|
||||
5. `paymentChannel = "wechat_mp"` 时后端创建待支付订单,并调用微信支付 JSAPI 下单生成小程序支付参数;本地 `orderId` 会作为微信 `out_trade_no` 传递,格式固定为 `rcg` 前缀 + 小写字母数字,长度在 6-32 字符内,满足微信支付 JSAPI 下单文档对商户订单号的限制。商品描述限制为 127 字符内,回调地址限制为 HTTPS、255 字符内且不携带 query/fragment。
|
||||
- JSAPI 下单请求必须显式携带 `Accept: application/json`、`Content-Type: application/json` 和 `User-Agent: Genarrative-WechatPay/1.0`;微信侧会把缺少 `User-Agent` 的请求返回为“Http头缺少Accept或User-Agent”。
|
||||
5. `paymentChannel = "wechat_h5"` 时调用微信支付 H5 下单,返回 `wechatH5Payment.h5Url`,前端跳转到该地址
|
||||
6. `paymentChannel = "wechat_native"` 时调用微信支付 Native 下单,返回 `wechatNativePayment.codeUrl`,前端在充值弹窗内把 `codeUrl` 渲染成二维码
|
||||
7. 所有微信真实渠道订单不提前发泥点或会员,只返回待支付订单、账户中心快照与对应支付载荷
|
||||
6. `paymentChannel = "wechat_h5"` 时调用微信支付 H5 下单,返回 `wechatH5Payment.h5Url`,前端跳转到该地址
|
||||
7. `paymentChannel = "wechat_native"` 时调用微信支付 Native 下单,返回 `wechatNativePayment.codeUrl`,前端在充值弹窗内把 `codeUrl` 渲染成二维码;该渠道只面向桌面微信内网页
|
||||
8. 所有微信真实渠道订单不提前发泥点或会员,只返回待支付订单、账户中心快照与对应支付载荷
|
||||
|
||||
兼容路径:`POST /api/runtime/profile/recharge/orders`
|
||||
|
||||
@@ -164,8 +169,8 @@ H5 与 Native 响应:
|
||||
- `success` 只表示微信客户端支付流程返回成功,前端随后调用 `POST /api/profile/recharge/orders/{order_id}/wechat/confirm` 由服务端查单确认;只有通知或服务端查单确认为 `SUCCESS` 才入账。
|
||||
- 小程序返回后,前端会对确认接口做短轮询,覆盖微信通知/查单结果与 web-view 恢复之间的秒级时间差;只有确认响应里的订单状态变成 `paid` 后,才触发父级 `profileDashboard` 刷新,确保“我的”页泥点卡片读取到最新余额。
|
||||
- `cancel` 和 `fail` 只复位按钮、刷新账户中心并通过全局支付结果模态展示,不调用入账逻辑。
|
||||
5. 移动网页含微信内 H5 首版统一走 `wechat_h5`:拿到 `h5Url` 后跳转;若微信内 H5 调起失败,失败态提示用户改用系统浏览器或小程序。
|
||||
6. 桌面网页走 `wechat_native`:充值弹窗展示二维码,用户扫码后点击“我已支付”,前端调用 confirm 接口短轮询确认;确认前不刷新父级余额。
|
||||
5. 手机微信内 H5 首版统一走 `wechat_h5`:拿到 `h5Url` 后跳转;若微信内 H5 调起失败,失败态提示用户改用系统浏览器或小程序。
|
||||
6. 桌面微信内网页走 `wechat_native`:充值弹窗展示二维码,用户扫码后点击“我已支付”,前端调用 confirm 接口短轮询确认;确认前不刷新父级余额。
|
||||
7. 支付结果使用页面级全局模态展示,不写回商品卡片或账户充值弹窗内部;充值弹窗只负责套餐选择、加载失败和下单失败。
|
||||
8. 弹窗内不写大段说明文案,只保留必要金额、泥点、会员权益和操作状态。
|
||||
9. 会员卡充值区以套餐卡片优先展示周期、价格和处理状态;移动端单列,桌面端三列,权益表允许横向滚动,避免小屏挤压。
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
| `phone_verified` | 是 | 是否已完成手机号验证。 |
|
||||
| `binding_status` | 是 | 账号绑定状态,固定为 `active`、`pending_bind_phone`。 |
|
||||
| `display_name` | 否 | 当前展示名快照,用于少量上游日志/观测;不是授权依据。 |
|
||||
| `device` | 否 | 登录时采集到的最小设备快照,仅保留 `client_type`、`client_runtime`、`client_platform`,用于后端按设备拦截真实微信支付路径。 |
|
||||
|
||||
## 6. 关键字段定义
|
||||
|
||||
@@ -264,7 +265,7 @@
|
||||
4. refresh token hash
|
||||
5. 风控状态、captcha 状态、封禁剩余时间
|
||||
6. 完整用户资料对象
|
||||
7. 审计日志、设备列表、IP、UA
|
||||
7. 审计日志、完整设备对象或列表、IP、UA
|
||||
|
||||
原因:
|
||||
|
||||
@@ -284,6 +285,11 @@
|
||||
"phone_verified": false,
|
||||
"binding_status": "pending_bind_phone",
|
||||
"display_name": "微信旅人",
|
||||
"device": {
|
||||
"client_type": "wechat_h5",
|
||||
"client_runtime": "wechat_embedded_browser",
|
||||
"client_platform": "ios"
|
||||
},
|
||||
"iat": 1713657600,
|
||||
"exp": 1713664800
|
||||
}
|
||||
@@ -302,7 +308,8 @@
|
||||
|
||||
1. 签发 access token
|
||||
2. 校验 `iss/exp/sub/sid/provider/roles/ver`
|
||||
3. 解析 claims 为统一 Rust 结构
|
||||
3. 规范化并透传 `device` 最小设备快照
|
||||
4. 解析 claims 为统一 Rust 结构
|
||||
|
||||
### 9.2 `module-auth`
|
||||
|
||||
@@ -344,6 +351,7 @@
|
||||
| 无 | `phone_verified` | 新增,表达手机号验证状态。 |
|
||||
| 无 | `binding_status` | 新增,表达待绑手机/已激活状态。 |
|
||||
| 无 | `display_name` | 可选新增。 |
|
||||
| 无 | `device` | 可选新增,承载最小设备快照,不包含完整 session 或敏感原始环境。 |
|
||||
|
||||
## 11. Express / Axum 请求上下文映射
|
||||
|
||||
@@ -361,6 +369,7 @@ Rust 侧建议升级为统一 claims 结构,例如:
|
||||
5. `token_version`
|
||||
6. `phone_verified`
|
||||
7. `binding_status`
|
||||
8. `device`
|
||||
|
||||
说明:
|
||||
|
||||
@@ -384,7 +393,8 @@ Rust 侧建议升级为统一 claims 结构,例如:
|
||||
1. `iss/sub/sid/provider/roles` 已明确冻结
|
||||
2. access token 与 refresh session 的职责边界已切开
|
||||
3. Axum、`platform-auth`、`module-auth`、`SpacetimeDB` 的使用边界已明确
|
||||
4. 后续可以直接按这份文档实现签发、校验与身份透传
|
||||
4. 登录设备快照的最小字段已明确,且可用于支付路径拦截
|
||||
5. 后续可以直接按这份文档实现签发、校验与身份透传
|
||||
|
||||
## 14. 依据文件
|
||||
|
||||
|
||||
Reference in New Issue
Block a user