# 三档通票 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 语法错误阻断。本次未修改这些无关业务。