server/laf-cloud/passCheckV2.API.md

146 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 三档通票 V2 后端接入
本次只交付独立 server 项目的后端;不包含客户端修改、支付平台商品创建或线上部署。默认关闭迁移入口。等级奖励沿用现有30级的免费/付费两列,没有第三列。
## 权益与时间
| 档位 | 价格(分) | 每次胜利进度 | 可领奖励 |
|---|---:|---:|---|
| 1 | 0 | 1 | 免费列 |
| 2 | 1800 | 1 | 免费列、付费列、每期开通礼包 |
| 3 | 3000;从2档升级1200 | 2 | 免费列、付费列、满级循环、每期开通礼包 |
前两档满级后的进度也保存,升级后能兑换;升级不翻倍历史进度。第二、三档共用相同的开通礼包,每期首次开通付费档时发一次;第二档升级第三档不重复发放已发礼包。第三档另外解锁双倍进度和满级循环奖励,第二档没有循环奖励。等级1免费奖励保持旧版的初始可领行为;后续等级以原表各级 token 的累计和解锁。
一期30天,结束后30天内可以补领。首次迁移在 `idcount[PASSCHECKTIME_ID]` 内原子固化 `passcheckV2Anchor`,后续按该锚点推算周期,不改写旧 `passcheckTime`。时间戳均为毫秒,唯一例外是资源 `userPowerTime`,沿用项目的 Unix **秒**到期时间。
上期有可领取的奖励时,`displaySeasonId` 指向上期,新期仍后台累计进度、从免费档开始;上期不可购买/升级。旧期免费玩家的未解锁付费列、不能领取的满级进度、循环余数不阻挡新期。上期领完或补领期结束后切到新期。超期奖励和余数不自动补发、不带入下一期。
## 配置与开放
新增模块:`passCheckV2`(POST入口)、`passCheckService`(内部共享模块)、`passCheckConfig`(既有等级奖励表),各有 Laf YAML 定义。共享模块不开放 HTTP 方法。
| 环境变量 | 设置 |
|---|---|
| `PASSCHECK_V2_ENABLED` | 只有字符串 `true` 允许尚未迁移的用户使用新版 read;默认关闭 |
| `PASSCHECK_V2_USERS` | 可选,以逗号分隔的用户 _id 灰度名单;空值表示不额外限制 |
| `PASSCHECKTIME_ID` | 继续使用独立后端现有配置;对应记录必须有有效的 passcheckTime |
| `PASSCHECK_PAY_CALLBACK_KEY` | 支付平台对应环境的服务端支付签名密钥;缺失时新版不提供购买选项 |
| `PASSCHECK_V2_CONFIG` | 下述 JSON;未配置时没有第三档商品与循环奖励 |
待填写的配置结构(null/空字符串表示尚未配置,**不能直接用于开放第三档**):
```json
{
"version": "1",
"premiumProduct": "",
"upgradeProduct": "",
"loopPoints": null,
"loopCoins": null,
"loopPowerSeconds": null,
"gift": null
}
```
- `premiumProduct`、`upgradeProduct` 必须是支付平台已经建立的不同商品,且都不能等于保留的 `battlepass`。
- `loopPoints`、`loopCoins`、`loopPowerSeconds` 都是正整数。每满 loopPoints 点按金币→无限体力交替发奖。
- `gift` 是第二、三档共用的礼包配置。缺失时新版第二档也不能下单;仅礼包及支付验签配置齐全时,可开放第二档而继续关闭第三档。`gift` 是非空 `{field, amount}` 数组,amount 为正整数。支持 `coinAmount`、`freezeAmount`、`hammerAmount`、`magicAmount`、`userPowerTime`;礼包里的 userPowerTime 表示新增秒数。
- 生产环境没有默认礼包、循环门槛、循环奖励数值。测试里的小额奖励和测试商品只存在于测试用例。
- 每期保存配置快照,已生效的奖励表和循环规则不随环境配置变化。未开放第三档的当期可以补齐配置,沿用该期既有等级表及已配置的共用礼包;缺失礼包可独立补齐,无需等待循环配置;已经开放的期次仅下一期采用新奖励配置。
- 关闭迁移开关不回退已迁移用户,也不停止他们的领奖/补单。迁移是单向操作,不能直接清除用户 V2 字段作为回滚。
## 客户端接口
统一 POST `passCheckV2`,请求带 `uid`、`token`、`action`。仅支持主用户库;`gameName: "iaa"` 返回 `UNSUPPORTED_GAME`。
成功:`{code:1, data:{...}, msg:"成功"}`。失败:`{code:0, data:{reason:"稳定错误码"}, msg:"..."}`。客户端按 reason 分支,不依赖中文提示。
### 读取
```json
{"uid":"用户ID","token":"登录token","action":"read"}
```
首次成功读取按开关迁移,之后返回:
- `current`:本期;`previous`:有可领奖励的上期,否则为 null。
- `displaySeasonId`:界面当前应展示的期次。
- `purchaseOptions`:当前合法商品及价格,单位分;上期遮挡期间为空。
- `revision`、`serverNow`、`resources`:版本、服务器时间、资源快照。
- 每期字段:`id/startsAt/endsAt/claimUntil/tier/points/unlocked/level/freeClaimed/paidClaimed/loopClaimed/giftGranted/config`。
- 派生字段:`multiplier`、`overflow`(满级后的总累计点数,包含已兑换部分)、`loopAvailable`(还可领几次)、`loopRemainder`(当前未凑满门槛的点数)。
- `levelProgress/levelTarget` 是当前等级内进度及门槛;恰好达成门槛时仍显示该等级,下次增加进度后进入下一等级。`unlocked` 是已经达成的奖励等级数量;未领取等级 = 1..unlocked 中未出现在对应 Claimed 数组的等级,付费列还需 tier>=2。
- `points` 是累计任务进度,循环领奖不会减小该值;`loopClaimed` 是已兑换次数。等级界面按配置 token 累计门槛显示。
### 一局结算
进入局前调用 `begin_round`,返回 `data.round:{id,seq,seasonId}`。重复 begin 在本局未结算时返回同一编号。
```json
{"uid":"用户ID","token":"登录token","action":"complete_round","roundId":"服务端id","roundSeq":1,"won":true}
```
胜利流程调用 complete;失败或主动结束用 `won:false` 完结。客户端持久化未确认编号,断线后重试原编号,不生成自己的编号或进度增量。同一编号只计一次;最近完成局重复请求返回 alreadyApplied,更老的编号返回 ROUND_INVALID 且不会加点。
跨期未结算局返回 ROUND_EXPIRED;客户端刷新并重新 begin。倍率以服务端成功结算时本期的档位计算。该接口用于接入现有胜利上报,不验证完整棋盘/对局过程;同一账号同时只保留一个活动局。
### 等级奖励
```json
{"uid":"用户ID","token":"登录token","action":"claim_level","seasonId":"期次id","track":"paid","level":10}
```
track 为 free/paid。后端验证等级、付费权益和补领期限,直接发放配置奖励;重复领取为成功空操作。返回 rewards 及更新后的 resources。
### 满级循环
```json
{"uid":"用户ID","token":"登录token","action":"claim_loop","seasonId":"期次id","expectedLoopClaimed":0}
```
expectedLoopClaimed 使用 read 返回的游标。本次领取所有已凑满门槛的奖励,金币与无限体力按顺序合并,保留余数。较旧游标重试为空操作,较新游标拒绝;想领取后续新积攒的奖励必须使用最新游标。前两档即使有 overflow 也返回 TIER_REQUIRED。
**客户端不要在成功响应后再次本地加奖。** 统一使用返回 resources 同步金币、道具及无限体力到期时间。购买前暂停并排空旧资源写请求,领奖/礼包到账后再同步快照。现有其他资源接口仍存在整值保存行为,接入时必须避免旧的资源请求在新版发奖后覆盖余额。本次没有重写全游戏资源系统。
## 支付接入与补单
Android 使用现有 `wx/orderPaySig`,iOS 使用 `wx/iosorderPaySig`,请求增加 `token` 和 `passCheckVersion:2`,itemid 从 read 的 purchaseOptions 获取。后台固定价格/数量,忽略客户端的 itemPrice/itemCount。新版订单在创建支付签名前已经写入订单库。
客服支付:先通过 iOS 签名入口预留并取得订单号,再把该订单号带入原 SessionFrom 流程。KeFuInfo 从预留订单读取实际价格/数量;checkIos 不再复制生成一个无 V2 元数据的订单。
订单包含 `passUid` 和 `passOrder`(期次、原档位、目标档位、商品、金额、渠道)。一个用户只允许一个有效未完成订单:同商品同渠道重试复用订单号;换商品、换渠道必须先完成或确认关闭旧订单。数据库使用预留订单号作为 V2 订单 _id,避免重试插入重复。
- 虚拟支付回调校验 `HMAC-SHA256(key, Event + '&' + Payload)`、生产 Env、非模拟推送、商品、数量、金额和归属。签名密钥只在服务端配置。参考[腾讯支付事件签名说明](https://intl.cloud.tencent.com/zh/document/product/1219/74865);正式开通前须在对应微信环境验证一次真实支付链路。
- iOS客服支付通过现有服务端商户查单确认 SUCCESS,校验订单号、付款人和金额。接口不会接受客户端自报“已付款”。
- `wx/getPayInfo`、`wx/getOrderReward` 对 V2 订单改走统一补单逻辑,不再允许客户端把未发放订单直接标记完成。
- `reconcile_order` 请求携带 outTradeNo;只能补本人的、后端已验证支付的 V2 订单。
- 状态/礼包/资源/订单已应用收据先在用户文档中条件更新,再将订单标为完成;订单标记失败后可重试。用户内的 appliedOrders 收据跨期保留,防止清理旧期后重复礼包。
- 登录补偿处理已验证订单;iOS登录查单继续处理未完成客服订单。完成订单重试可能仅返回 `{alreadyApplied:true,outTradeNo}`,客户端随后 read 获取最新状态。
- 旧期迟到支付在补领期内归属原期,不给新期倍率。超出补领期限、档位冲突等保留 `passReviewReason` 并写服务端日志,由运营处理,不自动转期、不自动退款。
- iOS商户查单确认 CLOSED 后写入 `passClosedVerified`,才允许切换购买路径。Android关闭需现有支付运维查单/关单流程提供已验证结果,调用内部 `recordPassOrderClosed`;没有开放客户端关闭标记接口。客户端取消和超时都不能证明平台已关闭。
## 迁移与兼容
旧 passCheck 字符串/对象的 `[1]`、`[2]` 是上期、本期。迁移保留 `legacySnapshot`,将 activate 映射为1/2档,保存已领取记录及明确的 giftGranted:true 礼包记录;不推算旧版已丢弃的满级溢出。旧档案没有礼包记录时不推断已发,后续付费升级按未发处理;若历史渠道曾额外发过礼包,迁移前需核实并补齐该记录。异常 JSON、异常期次拒绝迁移,避免覆盖原档案。
旧 battlepass 仍有未支付/未发货订单时返回 LEGACY_ORDER_PENDING,先完成旧流程或确认关闭后再迁移。五彩账号尚待选择数据时返回 MIGRATION_CHOICE_PENDING。已迁移用户不能通过五彩选择接口覆盖通票;需人工处理这类跨档案替换。
已迁移用户的旧 passCheck/passCheckLv 读写均返回 CLIENT_UPGRADE_REQUIRED,保存还有数据库条件保护。登录返回 `passCheckVersion:2` 和原始 JSON 字符串 passCheckV2,旧 passCheck 返回 null;通票展示以新版 read 为准。旧客户端不可继续操作该账号通票。
常用错误:FEATURE_DISABLED、TOKEN_INVALID、CLIENT_UPGRADE_REQUIRED、CONFIG_INCOMPLETE、PAYMENT_VERIFIER_UNCONFIGURED、ORDER_PENDING、ORDER_UNPAID、ORDER_CLOSED、TIER_CONFLICT、OLD_REWARDS_PENDING、SEASON_EXPIRED、LEVEL_LOCKED、TIER_REQUIRED、ROUND_INVALID、ROUND_EXPIRED、CONFLICT。CONFLICT 可刷新并重试同一请求;INTERNAL_ERROR 保留订单/局编号后重试。
## 验证和交付限制
运行:
```powershell
node --test laf-cloud/tests/pass-check-v2.test.mjs
node --test laf-cloud/tests/login-wucai-state.test.mjs laf-cloud/tests/monthly-card-renewal.test.mjs laf-cloud/tests/wucai-transfer.test.mjs
```
测试使用内存数据库条件更新、固定时间、虚拟商品、模拟支付查单及回调签名,覆盖并发、断线补单、跨期和三种支付入口,没有向线上数据库或支付平台发请求。
部署前需确认真实 Laf 数据库的 `updated` 返回值、缺失字段匹配 null、自定义 _id 唯一性与测试契约一致,并完成客户端资源同步接入。关闭迁移开关、补齐礼包/循环/商品配置、配置回调密钥并完成联调后,才对灰度用户开放。保留现有玩家状态和订单记录,不进行批量清空。
验证记录:新版接口及相关登录/月卡/迁移测试通过。独立核心类型检查命令为 `tsc -p laf-cloud/tests/pass-check-tsconfig.json`(使用最小运行环境声明,不替代真实 Laf SDK 类型检查)。全量既有测试存在19项失败,其中12项依赖独立仓库外缺失的客户端路径,7项 Jungle/五彩既有断言在原版本同样失败;全项目类型检查先被已有 drawHead.ts 语法错误阻断。本次未修改这些无关业务。