iNeed创作者文档

功能接入

本页目录

双人付费开局(可选)

免费联机不需要本能力。网页游戏通过可信宿主的 payments.admission.prepare/ready/get/cancel 完成双人一次性核销;联机 SDK 只传消息,不代替付款,也不自动收费。当前 Godot 绑定和旧宿主不保证支持本能力。Flutter App 的 native-v1 入口当前未提供 admission;网页宿主已支持不代表 App 同时支持。发布前分别检查目标入口的 hello 能力;缺少能力时应在购买、准备或开局前停止该收费联机入口,不能先收费后报不支持。

平台交接与能力检查

先确定商品、每局消耗数量、支持的模式与广告替代,再交平台配置。这里只支持每位玩家每局消耗 1 次消耗品;商品价格、购买确认、库存和广告资格由平台决定。示例商品名不是现成可用商品。

平台配置示意:

{"realtime":{"modes":["random","invite"],"maxPlayers":2,"build":"my-game-admission-v1","disconnect":"pause_then_exit","networkMode":"P2P_2","centralRelayFallbackEnabled":false,"admission":{"version":1,"rules":[{"mode":"random","productKey":"duel_ticket","quantity":1,"funding":["inventory"]},{"mode":"invite","productKey":"duel_ticket","quantity":1,"funding":["inventory"],"metadata":{"gameMode":"duel"}}]}}}

邀请的 metadata 规则由中央房间资料严格匹配;不能用客户端 context 选择更低价格。一个房间必须唯一命中规则。引入本开局协议使用新 build,避免新旧客户端被匹配到一起。仅平台配置不会替游戏补写资源加载、开局或恢复逻辑。

先调用 INeedHost.request('hello',{protocols:[1]}),检查返回 value.capabilities 含全部四个方法。缺少任意方法就明确提示当前入口不支持该收费联机模式,不退化为两个独立 consume 或免费放行。其他已支持的单机/免费入口可以保留。

库存与购买沿支付技能:payments.products / payments.inventory 读取平台商品与现有库存,不在游戏中推算价格或伪造库存。恢复日志沿存档技能及接口手册:使用游戏已有持久层,或将日志合并进账号存档,以 storage.load 的 version 作为 storage.save 的 baseVersion。保存未知结果先回读,冲突不能直接覆盖;不要为了保存联机记录抹掉原游戏进度。此能力要求在 ready 前确认日志已持久化,内存变量本身不满足刷新恢复。

时序与消费边界

  1. 先登录并获得每局权益:读取库存,必要时经平台确认购买。购买只是增加库存。不要先调用普通 consume/buy_and_consume,随后又调用 admission,这会产生两次不同消费。
  2. 进入同一匹配或中央邀请房间。邀请双方选人、换图、准备后,房主 startGame;等待大厅本身不建立 RTC。
  3. 连接 RTC,安装游戏消息接收器,预载本局资源,交换并核对规则、角色、地图、随机种子和本局编号。SDK onReady 只等待本地初始化,不能在其中等对端消息;参照 双方准备示例。
  4. 双方以相同 context、previousRoundId 各自 prepare;每人使用自己固定的 requestId。PREPARED 不扣库存,有效期 120 秒。
  5. 双方资源、协议和传输均可用后,各自用自己的 receiptId、共同 roundId/bindingHash 调 ready。两人都 ready 才由服务端在同一事务中核销双方。
  6. 本端读到 COMMITTED、resumable:true 且 start 与当前 roundId/bindingHash 一致后,再参加游戏自己的双端开战确认。对手的 P2P 消息不能代替本人可信回执。
  7. 取消或建连失败时,若已 prepare 并持有本人的 roundId/receiptId,再调用 admission.cancel 并核实结果;尚未 prepare 就没有可取消的金融轮,不能伪造 ID。中央邀请房还须按邀请示例捕获同一房间、代际和 epoch,调用 room.cancelStart 并核实已回到 waiting。admission.cancel 不会替你取消房间的 starting 状态;旧回调不能取消新一局。已提交消费不会因任一种取消/断线变成退款。未知结果保留记录并查询,不能显示“未扣费”。

已提交后双方同时进入玩法仍是游戏自己的协议。浏览器崩溃、客户端永久离线等情况不能仅靠核销接口保证玩家已完整享受一局;需要明确恢复/客服策略,不承诺自动退款。

方法参数

调用形式为 INeedHost.request('payments.admission.'+action, params),返回 {ok:true,value} 或 {ok:false,error:{code,message}}。params 不带 action、账号、作品 ID、价格或广告 token;可信宿主补充可信身份。

每个方法都含 v:1。当前房间参数为 room:{code,clientId},取当前 transport/ServerRoom 的真实身份。code 为 12 位大写十六进制,clientId/requestId 为 16–80 位字母、数字、下划线或连字符。请求 ID 在同一操作重试时保持不变;可用 SDK 的安全 ID 实现,不依赖旧 iOS 缺失的 crypto.randomUUID。

方法 其他参数 作用
prepare room、requestId、previousRoundId、context、funding、productKey(inventory 可省略,rewarded 经可信宿主时必填) 建立本人同意,尚不消费。第一轮 previousRoundId:null;新一轮使用已结束上一轮的 roundId。funding 为 inventory 或已配置的 rewarded。
ready room、requestId、roundId、receiptId、bindingHash 标记本人可开战;两人都满足条件才原子核销。
get 当前轮 room 查询当前轮。不能同时带 roundId/receiptId。
get 历史回执 roundId、receiptId 不带 room;只能查本人回执,可用于未知提交结果核对。
cancel room、requestId、roundId、receiptId 取消尚未提交的一轮;已提交则返回既有事实。

roundId/receiptId 是服务端返回的 48 位小写十六进制 ID,bindingHash 是 64 位小写十六进制摘要,不要自己生成。context 为最多 16 项、JSON 最多 2048 字节的普通对象;键为 1–64 位字母数字/下划线/连字符,值为不超过 128 字符的字符串、有限数值或布尔值。两端内容必须一致,不能放各自不同的昵称、角色排序或本地时间戳。

下面只展示方法形状;persistIntent、loadResources、双方协议和 UI 属于游戏。必须先保存固定请求,再发送,不能在重试时重新生成 ID:

const params = {
  v: 1, room: {code: transport.code, clientId: transport.roomClientId},
  requestId: savedPrepareRequestId,
  previousRoundId: previousRoundId,
  context: agreedContext, funding: 'inventory', productKey: 'duel_ticket',
};
const response = await INeedHost.request('payments.admission.prepare', params);
if (!response.ok) handleAdmissionError(response.error);
// 只有 ok 且 value 为当前轮 PREPARED 时,保存本人 receiptId / roundId /
// bindingHash。完成游戏准备后再用固定的 ready requestId 调 ready。

get({v:1,room}) 返回 EMPTY 时才是尚无轮;若为 PREPARED,使用其 previousRoundId 加入当前轮,不把它的 roundId 当下一轮 previousRoundId。若为 COMMITTED/CANCELLED,只有玩家明确开始新一局且上一局已收口,才将该轮 roundId 用于新 prepare。迟到回复不能改变新一局。

状态与恢复

state 含义与处理
EMPTY 尚无当前轮;有 previousRoundId:null、sequence:0。
PREPARED 未消费;包含 roundId、bindingHash、context、productKey、expiresAt 等。本人已 prepare 时有 receiptId/funding、ready;peerReady 表示对方准备。viewerNotPrepared:true 时不能直接 ready。
COMMITTED 本人消费已提交,含 receipt:{id,productKey,quantity,funding,committedAt}。只有 resumable:true 且 start 匹配当前会话才能继续本局;历史回执查询始终 resumable:false,不能用旧收据开启新局。
CANCELLED 本轮未提交,charged:false,附 reason。重新开局需要明确新意图。
NOT_FOUND 本人历史消费回执未找到,不是“免费开战”凭证;结合固定请求和当前房间核对。

按账号、作品和局保存恢复记录:room/clientId、prepare/ready/cancel requestId、previousRoundId、context、roundId、receiptId、bindingHash、资金来源和最后可信状态。先保存再发 ready;持久化失败时停止进入提交阶段。只存恢复线索,不存账号令牌、广告凭证或把本地记录作为权益证明。账号切换后不将 A 的结果应用到 B,原记录仍归 A。

ready 超时/断流后先用相同身份 get 当前轮;房间已不可访问但有 receiptId 时查询本人历史回执。保留未知操作,有限重试、退避并向用户显示核实中;不能换 ID 再消费、偷偷开始下一局,或清掉未确认记录。COMMITTED 表示消费事实,不能回滚成未扣费。已提交但不可恢复时显示事实并按作品的恢复策略处理。

prepare/cancel 重试必须保留原参数和请求 ID。BINDING_CHANGED、STALE_ROUND、SESSION_CHANGED 等错误应重新核对房间/账号/局;不要自行更换身份“试到成功”。INSUFFICIENT_INVENTORY 仅代表明确余额拒绝,不能把网络超时当余额不足再买一次。UNSUPPORTED 停止该模式;BUSY、STORAGE_UNAVAILABLE、ACCOUNT_CHANGED 不能忽略。

广告和预览

rewarded 仅用于平台已允许的规则,并依赖可信宿主核验真实广告完成。prepare 必须显式传该广告对应的 productKey,供可信宿主定位授权;服务器仍会核对中央规则,不能以此改变价格或商品。普通展示广告 shown/closed、游戏计时器和 P2P 回执都不能授予资格;游戏不提交或保存 rewardedAccessToken。真实供应商完成、账号/商品绑定和重复使用拒绝需要单独验收。

预览只模拟 inventory,返回 preview:true/noCharge:true;rewarded 为 UNSUPPORTED。预览回执有容量和生命周期限制,不能证明真实钱包扣费、广告发奖或永久恢复。

验收最小集合

双端正常开局各消费一次;重复 ready 不多扣;一方建连/加载失败两侧均零消费;取消与第二人 ready 并发结果以可信回执为准;提交成功但回复丢失查回同一收据;当前/历史回执不能授予新局;换图/换人/退出/接管使旧准备失效;库存不足或广告拒绝不出现单侧新成功消费;同账号多页面、刷新与持久化失败正确收口。预览、真实数据库和正式广告/支付分别报告。