上市记:两家商店、两次被拒、一点都淡定不了

发布日记 · 2026 年 9 月 29 日 · 阅读约 20 分钟

English Deutsch Español Français Português Italiano 日本語 한국어 Русский Türkçe Polski Nederlands Bahasa Indonesia

Moneyflow 现在已登陆 App Store 和 Google Play。我们很想告诉你一切顺利。并不顺利。一路上,我们收获了 Apple 的两次拒审,教会了一个构建脚本说“不”,发现 Lambo 其实一直是一面方格旗,还修好了一个被 Google 耸耸肩放过、却被 Apple 揪住不放的 bug。

这是完整的故事,由我们的版本说明、运行手册,以及我们匆忙中写下的提交信息拼凑而成。它很长,有点贫嘴,而且如果你也打算发布自己的应用,里面有货真价实的建议。来杯咖啡吧,或者来一笔以资产抵押的小额贷款。

在 App Store 获取 在 Google Play 获取

日期版简要回顾

  1. 1.2.8 版本在 Google Play 上线。当晚随即发布公告,共六种语言。
  2. iOS 追平 Android,同样是 1.2.8,在我们抢救了一张没人看得清的启动画面之后。
  3. 第一次提交到 Apple 时没带应用内购买。第二次提交带上了全部 39 个。Apple 的第一次回复:需要补充信息(Information Needed)。
  4. 一次商标大扫除,以及构建 31,把丢失的一个密钥放回应用里。
  5. 第二次拒审,来自一台 iPad:两个登录按钮都报错,审核人员找不到商店。
  6. 罪魁祸首是一个缺失的参数。Android 一直悄悄地原谅了它。
  7. 构建 34,签名正确。用 Apple 登录终于能用了。
  8. 1.2.9 版本在 Google Play 上推出。
  9. 玩家告诉我们他们的手机在发烫。我们让游戏别再这样了。
  10. 一个周末五个版本,从 1.2.10 到 1.3.1。
  11. App Store 链接上了我们的主页。你现在就在这里。

上市之前:一款有承诺恐惧症的浏览器游戏

Moneyflow 起初是一款浏览器游戏。你选一个有真实工作、真实账单和真实薪水的角色,掷骰子,试着积累足够的被动收入,逃离朝九晚五,并买下两个梦想。两个都做到,是唯一的赢法,单人和多人都一样。游戏里还有一场婚礼、一份婚前协议和一次离婚,这些我们之后再说。

然后我们决定它该在你的手机上,结果发现应用商店完全是另一种游戏。没有骰子,没有卡牌,只有表单、问卷,和一个永远赢的庄家。我们之前写过 Google Play 的各种手续,见 把 Moneyflow 上架 Google Play 商店。这是它的续集:接下来在两大商店发生了什么,以及如果能回到过去,我们会对自己说什么。

第一回合:Google Play 说好(在索要了一切之后)

Google Play 先上场,它的表现就像你申请房贷时的银行:友好、周到,渴望看到你名下的每一份文件。数据安全表单、内容分级、账号删除页面、封闭式测试期。前一篇文章把这些全讲了,所以这里讲的是第一个版本上架之后我们改了什么。

我们不再手点了。全部 39 个应用内购买(18 个棋子、14 款骰子皮肤、4 个棋盘主题和 3 个套装,每一个都只是外观)都改用脚本创建,而不是手工,应用与两大商店之间由 RevenueCat 居中,所以购买逻辑在各处都一样。发布也通过 Android Publisher API 进行。一个脚本会打开一次编辑、上传包、指定轨道并提交,如果任何一步失败,它就删除这次编辑,所以失败的运行不会留下任何东西。这是整个故事里最整洁的东西。

Play 有它的怪癖。它拒绝任何它见过的构建号,包括你上传过却从未发布的,所以构建号就像游戏里的现金:花掉一个,它就没了,不管你有没有换到点什么。我们还有两个服务账号,只有一个被允许发布。另一个可以打开编辑、上传整个包、把它指定到轨道,然后在最后一步收到一个 403。费了那么多力气,蛋糕都摆上桌了,换来的却是一句坚决的“不”。

9 月 7 日,1.2.8 版本上线。当晚我们发布了六种语言的公告,因为既然要公开兴奋,不妨在英语之外,也用德语、西班牙语、法语、葡萄牙语和中文兴奋一回。

Google Play 说好。Apple 另有想法。

第二回合:Apple 欲擒故纵

安卓一路狂奔的时候,iOS 在 App Store Connect 里还停在 1.2.1 版本。9 月 10 日,它追上来了:1.2.8 版本,构建 30,十张新截图,以及八种语言的宣传文案。在这一切之前,还有“付费 App 协议”这件小事。在你接受它并提交银行和税务信息之前,Apple 不会让你创建任何一个应用内购买。在那之前每一次尝试都返回 409,这是 Apple 的说法,意思是“还不行,等我看过你的文件再说”。

然后我们通过 App Store Connect API 为全部 39 个购买写了脚本。第一次运行,其中 24 个失败了,报错是 UNEXPECTED_ERROR。我们又跑了一遍,结果一切正常。运行手册里现在有这个仓库里最禅的一句话:重跑,别调试。

不存在的启动画面

在真机上,我们的启动画面根本没法看,原因有两个。分镜板用方形图片铺满屏幕,所以在高屏手机上,它会放大到高度合适为止,把大约 54% 的宽度裁掉。字标和整个装饰边框都跑到了边缘之外。更糟的是,深色模式版本只是一个光秃秃的人物,大约占奶油色画布的 5%,这意味着每一部设成深色的手机上,徽标都是隐形的。

我们改成把图片完整放进屏幕、配上它自己的奶油色背景,去掉了深色版本,并换上了占画布 88% 的徽标组合。图片文件夹从 55 MB 缩到了 9.1 MB。第一印象不该有 55 MB 重。

怎么提交一个商店里什么都卖不了的应用

这里有一个伪装极好的陷阱。应用内购买是独立的审核项,而 Apple 不会审核一个不在你提交内容里的购买。我们第一次提交时,应用带着全部 39 个商品停在“准备提交”(Ready to Submit)状态就进了审核。如果它被批准了,我们就会发布一款以买卖为主题、商店却什么都卖不了的游戏。把它撤回来,花掉了我们一次“从审核中移除”(Remove from Review),也丢掉了我们在队列里的位置。

陷阱在于,Apple 允许你从“App 内购买项目”页面开始一份永远无法提交的草稿。上面没有应用版本,每个商品都会添加失败,提示“Unable to Add for Review”,而提交这份草稿则会失败,提示“add an app version for the selected platform”。这两条消息都没告诉你必须先把版本放上去。我们当然试过用脚本修这个问题。API 里压根没有提交与应用内购买之间的关系。我们试了三种写法,才接受这扇门只能走界面,而运行手册现在礼貌地写明了这一点,免得有人再试。

最终让我们开心的数字是四十:一个应用版本,外加为它准备的三十九件可买的东西。

第一次拒审:Apple 想更了解你

9 月 11 日,Apple 回复了。主题行是准则 2.1,需要补充信息(Information Needed)。这不是 bug,不是崩溃,也不是违规。Apple 没有报告任何缺陷。这是 Apple 发给审核历史较短的账号的那份问卷,并且附带要求提供录屏。与其说它是拒审,不如说是一次背景调查。

清单有七点:一段录屏,应用是做什么的、给谁用的,怎么进去并试用全部功能,用了哪些外部服务,是否有任何内容因地区而异,是否受监管或基于他人的素材构建,以及购买是怎么运作的。这些都是合理的问题,就像初次约会时的提问那样合理。解决方案中心把回复上限定在 4,000 个字符,而我们的回复用了 3,917 个,这是我们对一个刚认识的人说过的最长的话。

然后是录屏。Apple 想要真实的流程、真实的速度、真实的手机:一次冷启动、一局单人游戏、商店及其购买页、注册、登录、一个多人大厅,以及再把账号删除。四分钟、每秒 60 帧的 iPhone 录像大约有 240 MB,太大没法作为附件,所以 ffmpeg 把它压到了 10 MB 以内,界面依然清晰可读。我们把整份分镜清单都写了下来,因为如果这个项目教会了我们一件事,那就是我们会忘记自己是怎么做任何事的。

答案放在两个地方:解决方案中心的回复,以及应用详情里的审核备注,这样以后每一次提交,在到达时就已经回答过这些问题了。审核人员会逐字照着这些备注做。写给从没见过你应用的人,他只有四十秒空闲,手里拿着一台 iPad。这三点最后都会成真。

排练时还冒出一件事。删除账号有一次卡在“正在删除...”,第二次才成功。我们怀疑是服务器打了个盹,而在审核人员眼里,那和一个坏掉的按钮一模一样。审核人员在城里的时候,让你的服务器保持清醒。

商标大扫除

趁着 Apple 注意到了我们,我们把游戏过了一遍,找任何听起来像品牌的东西。Apple 的内容权利规定让我们去找,而我们发现的东西让我们不太喜欢。两个角色拥有“BMW 3 Series (CPO)”和“Honda Civic”。这些名字对游戏毫无增益,还招来一堆麻烦,所以现在它们是“跑车(CPO)”和“普通轿车”。“monopoly”这个词离开了我们的 App Store 关键词。

然后是 Lambo。结果 Lambo 棋子根本不是一辆车。它自始至终都是一个方格旗 emoji,所以旧名字承诺了一辆超跑,交付的却是一面赛旗。从财务角度说,这只是营销。它现在叫方格旗(Chequered Flag)。名为坦克(The Tank)的棋盘主题改名叫鲨鱼池(Shark Pool)。

我们没有打破的那条规则。

我们改了显示名称,而把每个商品 ID 原样留在原处。方格旗棋子在底层依然是 pawn.lambo,永远都是。这些 ID 在两大商店都已上线,与价格绑定,并写在套装授权里,改名会把这件物品从所有已拥有它的人手里收回。名字是文案。ID 是合同。

第二次拒审:iPad 事件

9 月 17 日,Apple 又回来了,这一次它找到了真正的问题。审核人员用的是一台 iPad Air(11 英寸,M3),运行 iPadOS 27,因为仅限 iPhone 的应用会在 iPad 上以兼容模式运行,并在那里接受审核。一共有两项发现。准则 2.1(a):点击“继续使用苹果”或“继续使用谷歌”会显示错误。准则 2.1(b):审核人员找不到我们的 39 个应用内购买。

两个都是我们的错,而且在某种意义上,是同一个错。

那个 bug

登录插件每次调用都需要一个 options 对象。在 iOS 上,如果缺了它,插件会在任何登录面板有机会出现之前就以“Missing provider or options”拒绝。在 Android 上,同一个插件会悄悄假定一个空对象然后继续。于是同一行代码顺利通过了 Google Play 的审核,却在 Apple 那边第一次点击时就崩了,每一部 iPhone 和每一台 iPad 上都是如此。

一个缺失的参数。Android 原谅了它。iOS 没有。

没人找得到的商店

购买是另一桩误会,恰好和第一桩严丝合缝。我们旧的审核备注告诉审核人员,买东西需要登录。而登录是坏的。所以审核人员从未到达商店,这是我们自己造出来的闭环。

仔细一看,事情更糟,因为商店还有另外三种让人失望的方式。如果商品列表加载出来没有任何套餐,商店就把这当成“没有东西可卖”,把你还没拥有的每件物品都藏起来。三个套装(5 件棋子、主题和新手角色)从 6 月起就一直被藏着。而一个以访客身份买下 5 件棋子套装的人,会付了钱却什么也没解锁,因为我们的服务器只对已登录的玩家把套装展开成其中的物品。而访客,当然,恰恰就是审核人员。拿了别人的钱却递过去一个空盒子,是一堂很好的理财课。只是不是我们想教的那一堂。

修复清单,留个记录。传入那个参数。让两个登录面板都附着到 iPad 上应用的活动窗口,因为插件锚定在第一个窗口,而在 iPadOS 上那不一定是可见的那个。教会商店:零个商品加载出来意味着“尚未加载”,而不是“空”。把套装带回来。教会应用为访客展开套装。并重写审核备注,用大白话说明购买不需要登录,并给出到商店的确切路径。

结果就是 1.2.9。它的版本说明以两句话开头,这两句话是我们付出最多的:“登录修复:刻意点击‘继续使用谷歌’会显示账号选择器,原生登录不再报错。商店即使在价格加载之前也始终列出物品。”两句话,十天的人生。

构建 32 和 33:我们不提的那两个

你会以为我们直接把修复发出去就行了。我们发了,发了两次,两次都是错的。在 TestFlight 构建 32 和 33 上,谷歌登录能用,但用 Apple 登录,也就是 Apple 自己的测试计划里 Apple 自己的按钮,失败了,报错是 AuthorizationError 1000。

事后看原因很明显,事前却完全看不见。权限(entitlement)随代码签名一起走。我们的上传脚本在归档时关掉了签名,把签名留给导出步骤,而导出步骤可以给应用签名,却无法给它从来没有过的权限。用 Apple 登录的权限根本没进构建。用开发者的话说,这个应用没穿裤子就出门了。

上传脚本现在在归档时就签名,并拒绝上传任何签名里缺少该权限的归档。我们造了个保安。它查证件。构建 32 和 33 已经作废,而构建 34,那个衣冠整齐的版本,就是我们送回 Apple 的构建。

然后 Apple 说好了。

热得发烫

到 9 月 24 日,玩家告诉我们他们的手机在发热。iPhone 15、iPhone 16 Pro Max,网页版也有点烫。我们很想说那是复利的纯粹魅力。其实是一个计时器。

游戏每秒给一个计数器加一次,所有模式都是如此,每一次跳动都会把整个游戏重新渲染一遍:棋盘、每个格子、每个面板。永远如此。与此同时,动画棋子在高刷新率手机上每秒重绘多达 120 次,去画一个每秒 30 帧的动画。每张卡牌背后的全屏模糊每一帧都要重新计算,因为棋子一直在它下面移动。而让棋盘看起来活灵活现的光晕,动画的是盒阴影,这会强迫每一帧都重绘。

四个我们都修了。计数器变成了单个超时。棋子只绘制整帧,并在离开屏幕、开启减弱动态效果或被卡牌盖住时暂停。光晕和光环改为对不透明度和位置做动画而不是阴影,并且脉动三次后歇下来。棋盘的空闲工作量在十秒无头测试中从 624 毫秒降到了 15 毫秒。打开一张卡牌、棋盘上有龙棋子时,它从 1,188 毫秒降到了 11 毫秒。我们的动画现在表现出的自制力,比我们在这个项目里其他任何地方表现出的都多。

它以 1.2.10 的名义发布,头条是“手机更凉快”,同时带来了更公平的裁员规则。被解雇时只有你的薪水会停。副业收入照付,账单照样到期。被炒鱿鱼就该疼。但不该把你的副业一并删掉。

账本:每份版本说明,连起来读

我们把版本说明都放在一个文件里,这个文件同时喂给两大商店。按顺序读,它们构成一种奇怪的日记,所以这里是加上了潜台词的版本。日期是提交日期。该文件从 1.0.1 直接跳到 1.2.6,所以中间发生的一切都没有留下日记。下面的每个版本号都链接到它自己的文章。

1.0.0

Moneyflow 首次发布。建立财务帝国,摆脱老鼠赛跑,追逐梦想。

认真、简短,还有点害怕。看,当初多小。

1.0.1

Google Play现在可在 playmoneyflow.com 进行网页购买。多项稳定性与体验优化。

App Store稳定性与性能优化。修复了财务报表面板和骰子动画中的多个问题。

同一个版本,两套说明。两大商店对你能告诉别人什么有不同的看法,所以我们各留一套,并尽量不混在一起。

1.2.6

税收来了:薪水征所得税,副业收入征 15%,盈利出售征 15% 资本利得税。角色现在拿的是 2025 年美国的真实薪水。租金和企业回报跟随经济——萧条时微薄,繁荣时丰厚。

税务官登场。没人要求,人人都需要。游戏里的每一项回报现在都按税后来评判,因为那是唯一重要的回报。

1.2.7

可对交易、报价和贷款报价进行谈判:一次五五开的抛硬币,条件好 20% 或差 20%。在竞标战中选择卖出哪处房产,在离婚中选择哪些资产归对方——或者签一份婚前协议。

讨价还价的天真初版。同时也是婚前协议的首次登场,这说明了我们当时在想什么。

1.2.8 9 月 7 日

讨价还价两次:先谈判,再重新谈判——但对方可能转身走人(先是 10%,然后是 15%)。卡牌贴合你的人生:婚礼之前不会离婚,没有股票不会卖股票。离婚分割你的净财产,前任可以承担贷款。

浪漫的那个版本。问两遍,他们就急了。直到这个版本之前,牌组都可能在你还没结婚时就发给你一张离婚卡,这比拉斯维加斯的教堂还混乱。它也是把我们带到 Apple 家门口的那个版本。

1.2.9 9 月 22 日

登录修复:刻意点击“继续使用谷歌”会显示账号选择器,原生登录不再报错。商店即使在价格加载之前也始终列出物品。

在胁迫下写成,被两次拒审和一个缺失的参数所迫。

1.2.10 9 月 26 日

手机更凉快:游戏不再让你的设备在你玩的时候一直高负荷运转。空闲动画脉动几次就会歇下来,棋盘也不再每秒重绘。裁员更公平:只有薪水停发,副业收入照常入账,被跳过的发薪日开支仍然到期。

我们不再让你的手机发烧的那个版本。

1.2.11 9 月 26 日

购买菜单新增:城市公寓、干洗店和路边汽车旅馆。… 每张卡牌都有自己的插画,你的选项就在卡牌正下方。邀请朋友加入私人多人房间。

最拥挤的版本,也是寿命最短的之一,因为 1.2.12 就在当晚到来。私人房间是所有有朋友的人的头条:一位房主,两到四个座位,和一个六位字符的房间码。

1.2.12 9 月 26 日

现金不够?你现在可以选择如何补上,还能看到离破产有多近:连续紧急贷款、剩余借款空间和剩余发薪日。紧急贷款现在比信用卡便宜。

破产现在带上了倒计时,这是我们所知教人恐惧最友好的方式。紧急贷款现在比信用卡便宜,这比现实生活能给你的还要多。另外修好的还有:关闭卡牌后骰子会自己掷,这要么是个 bug,要么是我们最消极攻击的功能。

1.3.0 9 月 27 日

24 张新卡牌,从路边汽车旅馆、一家 SaaS 公司到大空头,外加稀有度:少见和稀有的卡牌现在会出现。隐藏卡牌查看棋盘,再点“返回卡牌”。裁员和紧急情况不能再被关掉。

二十四张新卡牌,一套稀有度系统,以及悄悄成为本月最重要的变化:卡牌现在从服务器送来。下面还会细说。

1.3.1 9 月 27 日

以资产抵押借款:任何没有贷款的房产、企业或股票,现在都可以担保一笔更便宜、只付利息的贷款,利率为市场利率加 0.5%。

又是杠杆。这是一款金融游戏,我们管不住自己。它在当天晚些时候跟在 1.3.0 之后。

1.4.0 10 月 1 日

全新专家级机器人会规划每一步并研判经济走势,所有机器人现在都能购买梦想、获胜,并以资产抵押借款。经济更有分量了:利率随繁荣与衰退周期变动,衰退期减税,你的租金在繁荣期上涨、衰退期下降。

机器人学会赢的那个版本。直到现在,一个都赢不了,因为没有一个买过梦想。专家级机器人到来,经济开始让利率、税收和租金随着它的情绪变动,应用还学会了另外八种语言。

1.4.1 10 月 1 日

… 房贷现在分 25 年还清,企业贷款分 15 年,车贷分 6 年;银行贷款只付利息,贷款还款最高可达收入的 67%。…

房贷终于有了结束日期。贷款方面的改动在 1.4.0 定稿之后才落地,所以当晚就作为 1.4.1 发布,并带上了主屏幕上的一张新闻卡。

1.4.2 10 月 1 日

一个 bug,在 1.4.1 之后不到二十分钟修好:过桥贷款按钮现在提供的是银行真正会批准的金额。它没有自己的商店说明。

五个版本的周末。

9 月 26 日,星期六

9 月 27 日,星期日

提交时间,索非亚时间。并非每个版本都以自己的身份到达每个商店:在 Google Play 上,1.2.12 取代 1.2.11 进入了正式版,在 iOS 上,它也是取代后者提交的。同一个周末里,牌组新增了 130 幅插画,用代码绘制。

最好的更新,是不需要任何人批准的那一个

到那个周末结束时,我们学到了一件有用的事。发布更新最慢的部分不是构建它,而是等商店来看它。所以 1.3.0 做了点不一样的:它教会应用接收投递。

新卡牌现在从我们的服务器送来,所以大多数新内容不再需要应用更新。装好这套机制只用了一次应用发布。之后,新卡牌只是一次部署的事,翻译和插画也包括在内。

商店也是同样的运作方式。Apple 会单独审核一个新的应用内购买,不需要新的构建,所以一个新棋子也不再需要一个新版本。

巧妙之处在于安全网。没人会被发到自己的应用玩不了的卡牌,所以新旧版本可以同桌而无问题。当我们发明真正全新的机制时,还是得像所有人一样回到队列里去。

同一个版本带来了稀有度,这意味着有些卡牌出现的频率远低于其他。低多少是我们的秘密,不过最稀有的那些,出现的频率大约和 App Review 一次过审差不多。我们抽到的是一张普通卡。

记分牌

写给爱看电子表格的人。

刚开始时我们会对自己说的话

过去要点一晚上的活,现在每个商店只需一次版本号提升、一份说明和寥寥几条命令。等待是我们唯一无法自动化的部分。其余的一切都进了这份清单。

  1. 在应用内购买之前,先把版本放进提交。Apple 会让你反着来,然后拒绝告诉你为什么。
  2. 为从没见过这个应用的审核人员写审核备注。他们只有四十秒和一台 iPad,而且会逐字照着备注做。把通往一切的确切路径交给他们。
  3. 永远不要在商店前面放一道登录。访客也有钱包,而审核人员就是访客。
  4. 给归档签名,然后让脚本拒绝上传没有穿好权限的归档。给自己造一个保安。
  5. 让缺失的密钥大喊大叫。构建 30 发布时没带支付密钥,没有任何东西大声抱怨,于是商店就是一片空白。无声的失败,就是延迟的拒审。
  6. 即使代码共用,也要在两个平台上测试。Android 原谅了 iOS 拒绝的东西,一行代码有了两种性格。
  7. 给商品改名,但绝不改它们的 ID。名字是文案。ID 是合同。
  8. 写成脚本,让它可重复,写下来。Apple 的 API 有时候就是需要缓一缓,所以先重跑,再调试。我们的运行手册之所以存在,是因为我们把一切都忘了两遍。
  9. 两大商店规则不同,就分开保存版本说明。同一个版本,不同的句子。
  10. 把内容从服务器发出去,把商店队列留给新机制。最好的更新,是不需要任何人批准的那一个。
  11. 在你的玩家真正拥有的发烫硬件上测试。模拟器从来不会脸红。一台 iPhone 16 Pro Max 会。
  12. 审核人员在城里的时候,让服务器保持清醒。一台睡着的服务器,看起来和一个坏掉的按钮一模一样。

来吧,带我们试驾一圈

Moneyflow 在 App Store 免费,在 Google Play 免费,在任何浏览器里也免费。没有广告,开始游戏不需要账号。商店里的一切都只是外观。里面没有任何东西能帮你获胜,这在一款关于钱的游戏里,要么非常有原则,要么是非常糟糕的生意。

接下来是什么?更多卡牌,它们现在从服务器送来;更多和朋友一起玩的方式;以及,如果宇宙仁慈的话,整整一周没有版本号。

在 App Store 获取 在 Google Play 获取 在浏览器中游玩

棋盘上见。