13 KiB
三档通票 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/空字符串表示尚未配置,不能直接用于开放第三档):
{
"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 分支,不依赖中文提示。
读取
{"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 在本局未结算时返回同一编号。
{"uid":"用户ID","token":"登录token","action":"complete_round","roundId":"服务端id","roundSeq":1,"won":true}
胜利流程调用 complete;失败或主动结束用 won:false 完结。客户端持久化未确认编号,断线后重试原编号,不生成自己的编号或进度增量。同一编号只计一次;最近完成局重复请求返回 alreadyApplied,更老的编号返回 ROUND_INVALID 且不会加点。
跨期未结算局返回 ROUND_EXPIRED;客户端刷新并重新 begin。倍率以服务端成功结算时本期的档位计算。该接口用于接入现有胜利上报,不验证完整棋盘/对局过程;同一账号同时只保留一个活动局。
等级奖励
{"uid":"用户ID","token":"登录token","action":"claim_level","seasonId":"期次id","track":"paid","level":10}
track 为 free/paid。后端验证等级、付费权益和补领期限,直接发放配置奖励;重复领取为成功空操作。返回 rewards 及更新后的 resources。
满级循环
{"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、非模拟推送、商品、数量、金额和归属。签名密钥只在服务端配置。参考腾讯支付事件签名说明;正式开通前须在对应微信环境验证一次真实支付链路。 - 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 保留订单/局编号后重试。
验证和交付限制
运行:
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 语法错误阻断。本次未修改这些无关业务。