内购成功为什么不等于发奖成功

人和 AI 一起做游戏 · 第 14 篇

《人和 AI 一起做游戏》第 14 篇:内购成功为什么不等于发奖成功,Kibble Street TD 历史开发画面与流程图。

本篇依据截至 2026 年 8 月 3 日的开发记录,回顾 2026 年 6—7 月及 8 月初的开发过程。正文中的“当前”、数值、画面、候选资源与验证结论均指当时快照,不代表现售版本的状态。

上一篇:游戏项目怎样接入 iOS 原生能力

玩家点下购买按钮,确认系统付款,随后看到“购买成功”。从体验上看,这像是一次连续动作。但对《Kibble Street TD》来说,平台确认付款只是中间节点:游戏还要确认这笔交易对应哪个商品、奖励能否写入、存档是否成功,以及同一笔交易有没有处理过。

只要其中一步处理不严谨,就可能出现两种最糟糕的结果:玩家已经付款却没有拿到商品,或者同一笔付款被重复发奖。前者损害信任,后者破坏经济系统。内购真正难的部分,因此不是调起付款页面,而是让交易从平台状态安全地落到游戏状态。

一次购买,其实有两本账

平台保存的是交易事实:购买了哪个商品、交易编号是什么、校验是否通过,以及交易是否已经结束。游戏保存的是业务结果:商品映射到什么内容、应当增加多少资源、这笔交易是否发过奖,以及结果有没有写入存档。

平台交易与游戏发奖是两本账
平台交易与游戏发奖是两本账

两本账通过交易编号连接。商品标识回答“买了什么”,交易编号回答“是哪一次购买”。如果只保存商品标识,那么同一个商品买两次无法区分;如果只相信页面上的成功回调,应用在发奖前被关闭后,也没有可靠依据继续处理。

当前商店还遵守另一个边界:商品与平台商品标识的对应关系来自同一份配置,玩家看到的价格则直接使用平台返回的本地化价格。游戏不自己拼接货币符号,也不把测试价格当正式价格。配置决定业务身份,平台决定交易和展示价格,两边各自负责自己掌握的事实。

正确顺序不能交换

一笔交易进入游戏以后,处理顺序是固定的:平台校验通过,核对商品映射,检查交易账本,发放奖励并登记交易编号,把全部变化写入存档,最后才通知平台结束交易。

内购完成链路的正确顺序
内购完成链路的正确顺序

“结束交易”必须放在最后。如果先结束,随后发奖或存档失败,平台已经不会再把它作为未完成交易交回来,玩家就可能永久丢失商品。

发奖与存档也不能拆成互不相关的动作。《Kibble Street TD》会先记录货币、背包、购买次数和交易账本的完整快照,再修改这些状态并保存。任一步失败,全部恢复到修改前。这样不会留下“货币加了但账本没记”“次数增加但道具没到”的半成品状态。

这条链路也经历过一次重要修正。只回滚购买次数和交易记录并不够,因为奖励可能已经改变货币或背包。真正安全的回滚必须覆盖一次购买触碰的全部状态,而不是只修复最后看见的两个字段。

交易编号是一道幂等闸门

“幂等”听起来抽象,放到内购里很直接:同一交易编号无论到达几次,奖励都只能发一次。

同一交易只能发奖一次
同一交易只能发奖一次

首次收到一笔合法交易时,游戏发奖、登记交易编号并保存。相同编号再次到达,而且仍对应同一商品时,游戏把它识别为已经发过奖,不再增加资源,只继续完成尚未完成的交易收尾。如果同一编号突然指向另一个商品,则直接拒绝,因为这已经不是正常重试。

这个账本不能随着礼包周重置而清空,也不能在玩家使用“重置游戏”后丢失。否则旧交易重新出现时,就会被误认为新交易再次发奖。它保存的不是玩家进度,而是已经履行过的付款责任。

两种失败,要走两条恢复路径

第一种失败发生在发奖或存档阶段。游戏恢复全部修改,不结束平台交易。应用下一次收到交易更新,或者重新启动扫描到未完成交易时,可以从头再尝试;成功后只会得到一次奖励。

第二种失败发生在游戏已经保存成功、但通知平台结束交易失败之后。此时奖励和交易账本都保留。平台再次送来同一交易时,幂等闸门会阻止第二次发奖,游戏只重试收尾。

发奖失败与收尾失败的恢复路径
发奖失败与收尾失败的恢复路径

这两个分支看似相近,恢复依据却完全不同:前者依靠“平台仍保留未完成交易”,后者依靠“游戏已经保存交易账本”。把它们混成一次普通重试,很容易在丢奖励和重复奖励之间摇摆。

应用重启后,交易仍要能回来

真实购买不会只发生在页面正常打开、网络稳定的理想时刻。玩家可能在系统确认后切走应用,进程也可能在发奖途中被终止。因此初始化内购服务时,除了监听之后发生的交易更新,还要主动扫描平台保存的未完成交易。

启动时恢复未完成交易
启动时恢复未完成交易

实时更新和启动恢复最终进入同一条完成链路,只有平台校验通过的交易才会进入发奖。未校验、待处理和用户取消都不会被包装成成功。这样恢复逻辑不是另一套特殊发奖代码,而是同一规则的另一个入口。

购买限制也必须分清发生时机。付款前可以根据周限购或当前状态阻止继续购买;一旦平台已经确认扣款,就不能因为配置后来降低限额而拒绝发奖。限制控制的是“还能不能发起下一笔购买”,不能推翻一笔已经成立的交易。

测试成功不等于真实支付通过

项目早期使用过直接成功开关,用来验证商品映射、发奖、存档和页面反馈。它能缩短业务开发周期,但绝不能成为正式支付失败时的兜底,否则平台没有确认交易,游戏却会凭空发放付费内容。

当前这个开关已经关闭。受控协议检查也覆盖了两条关键异常:发奖失败时不结束交易,恢复后只发一次;收尾失败时保留奖励和账本,恢复后只重试收尾。这些检查已经通过。

但它们仍不等于真实支付验收。Sandbox、TestFlight、真机购买、付款后终止进程再恢复,都还需要在真实环境中完成。另一个提交前缺口是异常交易账本:正常重置与版本清理会保留合法账本,但账本自身无法读取时,发布规则要求直接阻断,当前这条异常分支还没有达到要求。

内购验证的完成项与待办项
内购验证的完成项与待办项

把这些状态分开写,不是降低完成度,而是避免一句“内购已经接好”掩盖真正的风险。代码顺序正确、受控故障能够恢复、真实平台购买成功、极端中断不丢单,是四个不同结论。

人和 AI 分别负责什么

AI 适合沿着交易编号追踪完整调用链,对照平台更新、发奖、存档、交易账本和收尾顺序,并主动注入保存失败、重复回调和收尾失败,确认每条路径留下什么状态。它也能发现“回滚只覆盖部分字段”或“正常清档保留账本,但异常账本被清空”这类跨模块断点。

人负责定义不能妥协的业务规则:已经扣款的交易必须履行,同一交易不能重复发奖,什么情况下必须阻断发布。真实账号、真实设备、平台后台配置和付款确认也必须由人完成,不能由模拟结果代替。

如果你想开始

先不要从购买按钮开始。先写清楚六个问题:谁确认交易有效,商品怎样映射,交易编号存在哪里,发奖和存档能否一起回滚,什么时候结束交易,应用重启后从哪里恢复。

然后只测试两次故障:让存档在发奖时失败一次,再让交易收尾失败一次。第一次恢复后应当只发一份奖励;第二次恢复后不应再次发奖。能稳定回答这两道题,才有资格继续做真实平台验收。