关于 billing-doctor
Billing Doctor 是面向自建 Google Play Billing 后端开发者的本地命令行工具。它按有文档依据的规则分析购买时间线并展示证据,默认不发送数据。
主要功能
- 01按 97 条规则诊断时间线
- 02验证并脱敏时间线数据
- 03解释订阅状态和 RTDN 消息
- 04筛选规则并输出机器可读结果
- 05可选获取 Google 状态
适用场景
- 01根据日志和支持记录排查订阅故障
- 02在 CI 中检查时间线的高严重性问题
- 03解码 RTDN 消息并确定后续步骤
MCP 相关
它可通过标准输入输出运行 MCP 服务,提供时间线诊断、状态解释、RTDN 解码、文本脱敏和规则查询等六种工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
6收录的工具列表 · 更新于 3 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| rules | array | 否 | 仅运行这些规则 ID |
| timeline | any | 是 | 时间线,可以是对象或 JSON 文本 |
详细介绍
账单诊断
Billing Doctor 会读取 Google Play 购买交易的处理情况,并告诉你问题出在哪里。它在你的机器上运行,不会调用任何服务,也不会更改 Google 中的任何内容,并会展示依据。与 Google 无关。
它面向使用 Node、Python、PHP、Go 或 Java 自建 Google Play Billing 后端的开发者,前端使用 Capacitor、Flutter、React Native 或原生 Android,且没有选择订阅平台。当订阅在晚上 11 点出了问题时,你就可以运行这个工具。
二十秒上手
下面的 fixture 随仓库一同发布,因此请从代码检出目录开始:
git clone https://github.com/lazytitan30/billing-doctor && cd billing-doctor
npx billing-doctor diagnose fixtures/rules/G2-void-then-active.json
(相同的 fixture 也随 npm 包一同发布:执行 npm install -g billing-doctor 后,它们位于 $(npm root -g)/billing-doctor/fixtures/ 下。)
billing-doctor 0.1.1: fixtures/rules/G2-void-then-active.json, 11 events, 1 finding: 1 high
HIGH
G2 Voided purchase, then a fetch reading ACTIVE, and no revoke [certain]
evidence #8 rtdn voidedPurchase tok_a1 [m-2] answered 200 (2026-09-04T10:00:00Z)
#9 api subscriptionsv2.get tok_a1 → 200 SUBSCRIPTION_STATE_ACTIVE ack ACKNOWLEDGED (2026-09-04T10:00:01Z)
mechanism tok_a1 was voided at #8 (the money went back); the fetch at #9 still answered ACTIVE and the ledger never revoked, so the refunded user keeps the tier.
Google "(1) REFUND_TYPE_FULL_REFUND - The purchase has been fully voided."
RTDN reference: VoidedPurchaseNotification, read 2026-09-09: https://developer.android.com/google/play/billing/rtdn-reference
observed Seen in a live app, 2026-08: after the voided-purchase notification, purchases.subscriptionsv2.get still answered SUBSCRIPTION_STATE_ACTIVE ...
next Revoke tok_a1 now. In the handler, act on voidedPurchaseNotification directly: mark refunded and revoke, regardless of what subscriptionsv2.get answers afterwards.
fix runbook G2 (billing-doctor rule G2)
1 finding: 1 high
exit 1: a high finding is present
每项发现都包含其依据的事件、问题机制、带日期链接的 Google 规则原文、置信度(certain、likely、possible)以及下一步要检查的内容。退出码 1 表示存在高优先级发现,因此该命令可用于拦截 CI。
功能
你需要提供一条时间线:一个 JSON 文件,记录某一笔购买发生的情况,由你的日志、数据库、Cloud Console 和支持工单整理而成。其中包括 Google 发送的通知、API 响应、后端写入的内容,以及用户所说的话。令牌使用假名;文件中不会包含任何真实信息。
它会根据 97 条规则,分成十一组,对时间线进行检查。每条规则都基于 Google 官方文档中的一句话,逐字引用,并附有 URL 和阅读日期:
| 分组 | 检查内容 |
|---|---|
| A. 配置与权限 | 完全没有通知、401/403、主题位于其他项目、否定确认、未经过身份验证的端点、在 2026-08-31 门槛后仍使用低于 Billing Library 8 的版本、未启用一次性购买通知 |
| B. 购买与确认 | Google 已退款但尚未确认的购买、在 PENDING 期间进行确认或发放权益、已确认但未消耗的消耗型商品、验证前发放权益、重复发放权益、客户端未收到通知的确认失败、预付周期、将多数量购买当作一件处理、因订阅尚未确认而阻止方案变更、文件中尚未退款且始终未确认的购买 |
| C. 通知 | 同一消息被应用两次、通知乱序、未重新获取数据就写入、收到无 ID 的消息时撤销、未处理的类型、将拒付审查当作退款、应用测试通知、始终未确认且未应用的消息 |
| D. 状态解读 | 从错误来源读取到期时间、将 CANCELED 和宽限期视为无访问权限、将暂停和暂停续订视为仍可访问、EXPIRED 仍发放权益、忽略行项目、使用已弃用的 v1 资源、将测试购买计入、将预付购买视为自动续订、令牌使用时间超过六十天窗口 |
| E. 生命周期事件 | RECOVERED 后未重新发放权益、将 RESTARTED 视为新令牌、DEFERRED 后仍使用旧到期时间、硬编码宽限期和暂停期、将计划取消和计划暂停视为立即生效、已弃用的类型 8、实际上由未接受的涨价导致的流失 |
| F. 升级与关联令牌 | 旧令牌仍在发放权益、提前为 DEFERRED 替换项发放权益、重新注册关联到了错误的令牌、按比例计费后未重新读取到期时间、应用外重新订阅后未建立关联、升级时丢失混淆账户 ID、对单位时间价格更低的方案使用按比例收费模式 |
| G. 退款与作废购买 | REVOKED 后仍发放权益、作废记录与读取到的状态不一致、将部分退款当作全额退款处理、未扫描作废购买、退款时未撤销、应撤销时却取消、退款涉及的订单不是最新订单 |
| H. 账户绑定与恢复 | 无法映射的通知、原始账户 ID、对共享令牌使用 findOne、恢复时未调用 queryPurchasesAsync、未查询已暂停的订阅、Google 账户不一致 |
| I. 测试与 Console | 测试续订间隔几分钟就发生、生产环境中存在测试行、已获批准但尚未发布、12 位测试人员规则、已验证但未写入任何内容、测试人员购买后在三分钟后退款 |
| J. 账本完整性 | 发放权益时未使用幂等键、到期后仍保留令牌、时间线中包含真实数据、以本地时间记录到期时间、以订单 ID 作为行的键 |
| K. 设备与 Play Billing Library | 商品目录为空、断开连接时仍发起调用、无人排查已拥有的购买、可恢复的失败从未重试、不可恢复的失败仍被重试、DEVELOPER_ERROR、商品详情过期、回调重复、查询重叠、将基本方案 ID 用作商品 ID、购买从未到达服务端、将失败报告为成功、将取消视为错误、购买时因商品不可用而被拒绝、Billing Library 8 后仍依赖设备恢复已消耗购买 |
billing-doctor rules 会列出这些规则;billing-doctor rule B1 会完整打印其中一条规则。
安装
需要 Node 20 或更高版本。
npm install -g billing-doctor
也可以直接运行,无需安装:npx billing-doctor <command>。
命令
| 命令 | 功能 |
|---|---|
billing-doctor init | 根据几个问题填写策略区块,并写入一个空的 timeline.json |
billing-doctor validate timeline.json | 根据架构检查文件;如果令牌、用户 ID 或邮箱看起来是真实的,则发出警告 |
billing-doctor diagnose timeline.json | 运行所有规则;使用 --json 输出供机器处理的结果,使用 --rules B1,C3 运行部分规则,使用 --kit <path> 指定运行手册 |
billing-doctor state sub.json | 用通俗语言解释 purchases.subscriptionsv2 资源;--policy timeline.json 用于指出你的策略与 Google 不一致之处 |
billing-doctor rtdn message.json | 解码 Pub/Sub 推送正文:整数对应的类型名称、令牌和下一步操作 |
billing-doctor redact < logs.txt | 将购买令牌替换为稳定的 tok_ 化名,移除邮箱,遮蔽订单 ID,并删去私钥和 bearer 令牌 |
billing-doctor rules 和 billing-doctor rule <id> | 规则目录;以及单条规则的 Google 引文和运行手册条目(若提供了工具包) |
billing-doctor mcp | 通过 stdio 提供与六个 MCP 工具相同的功能 |
退出代码:0 表示没有需要报告的内容;1 表示发现高风险问题(或无法解码输入);2 表示用法或文件错误。
时间线
docs/timeline-format.md 是格式规范。事件分为六种:app(设备报告的内容)、api(对 Play Developer API 的调用及其应答)、ledger(后端写入的内容)、rtdn(收到的通知)、support(用户所说的内容)、console(发布和配置更改)。policy 区块说明后端应如何运作,以便规则区分策略问题与 Bug。
{
"schema": "billing-doctor-timeline/1",
"app": { "packageName": "com.example.app", "billingLibrary": "8.0.0", "backend": "node" },
"policy": { "grantOn": ["SUBSCRIPTION_STATE_ACTIVE", "SUBSCRIPTION_STATE_IN_GRACE_PERIOD", "SUBSCRIPTION_STATE_CANCELED"], "ackWithinHours": 72 },
"events": [
{ "t": "2026-09-01T10:00:00Z", "kind": "app", "type": "purchase_result", "token": "tok_a1", "productId": "basic_monthly", "purchaseState": "PURCHASED" },
{ "t": "2026-09-01T10:00:02Z", "kind": "api", "call": "subscriptionsv2.get", "token": "tok_a1", "status": 200, "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE", "acknowledgementState": "ACKNOWLEDGEMENT_STATE_PENDING", "expiryTime": "2026-10-01T10:00:00Z" },
{ "t": "2026-09-01T10:00:03Z", "kind": "ledger", "op": "grant", "token": "tok_a1", "userId": "u_1", "idempotencyKey": "tok_a1:grant" }
]
}
fixtures/ 文件夹为每条规则存放一个最简时间线、三个符合实际情况的周记录,以及五个不会产生任何结果的正确生命周期。这些文件既是测试套件,也是了解此格式的最佳方式:fixtures/README.md。
使用编码 Agent
同一引擎也可通过 stdio 作为 MCP 服务运行,因此 Claude Code、Codex、Gemini CLI 或 Cursor 可以在你修复 Bug 时调用它。工具:diagnose_timeline、explain_subscription_state、decode_rtdn、redact_text、list_rules、get_rule。
将其配置为命令 npx,参数为 ["-y", "billing-doctor", "mcp"]。对于 Claude Code,此仓库也可作为插件使用(在检出目录中运行 claude --plugin-dir .,并使用技能 /billing-doctor:diagnose);对于 Gemini CLI,运行 gemini extensions install https://github.com/lazytitan30/billing-doctor。
Incident Kit
免费工具本身就已完整。付费版 Incident Kit 是一个文件夹,可帮助你更快修复问题:每条规则对应一个运行手册条目(用户报告的症状、问题机制、修复方法、需要添加的回归测试)、一组可在 CI 中针对你自己的处理程序重放的真实事件时间线、一个用于验证端点的 TypeScript 参考实现、一个 RTDN 处理程序和一个对账任务,以及适用于 Play Console、Cloud 设置、许可测试和 Billing Library 8 与 9 迁移的检查清单。将该文件夹放在工具旁边,命名为 ./billing-doctor-kit(或设置 BILLING_DOCTOR_KIT),然后运行 billing-doctor rule G2 即可打印运行手册。没有许可证服务器,也不会向外发送数据;工具包就是一组文件。
billingdoctor.dev — 每条规则对应一个运行手册页面、四十条事件时间线、参考后端和检查清单。
具体包含:
| 运行手册 | 每条规则对应一个页面:用户报告的症状、问题机制、注明查阅日期的 Google 规则原文、修复方法,以及需要添加的回归测试 |
| 事件 | 四十条根据真实故障整理的时间线,每条都附有它会产生的调查结果,另有五种必须保持静默的正确生命周期。在 CI 中针对你自己的处理程序重放 |
| 参考后端 | TypeScript:采用失败时关闭原则的账本验证端点、具备消息 ID 幂等性的 RTDN 处理程序、确认看门狗、已撤销购买扫描任务、对账任务、HMAC 账号绑定、数据保留清理任务和 Postgres 架构。可通过全部四十五条时间线 |
| 检查清单 | Play Console 和 Cloud 设置、许可测试、托管发布、Data Safety,以及 Billing Library 8 和 9 迁移 |
在十二个月内更新;当 Google 的规则发生变化或新增规则时,提供新的下载版本。
唯一的可选联网功能
diagnose --fetch 会使用你自己的服务账号密钥,针对时间线中的每个令牌读取 purchases.subscriptionsv2.get,并将 Google 的回复作为 api 事件附加到时间线中,这样根据日志构建的时间线就能反映 Google 对每笔购买的当前视图。此功能默认关闭;数据只会发送给 Google。
billing-doctor diagnose timeline.json --fetch --service-account ./key.json --token-map ./tokens.json --out augmented.json
时间线会保留其化名:tokens.json 将 tok_a1 映射到真实购买令牌,并保存在你的设备上;附加的事件携带化名,写入的文件绝不会包含真实令牌。该密钥需要在 Play Console 中拥有“查看财务数据”权限。仅获取订阅;不会获取一次性商品。
隐私
默认不进行网络调用。绝不收集遥测数据。你的日志绝不会离开你的设备。上文提到的唯一可选联网功能使用你自己的密钥,且只与 Google 通信。
它不是什么
它不是 SDK、托管服务、订阅平台、仪表板,也不提供法律或财务建议。它绝不会确认、消耗、退款、撤销、取消或延期任何内容。它无法诊断 Apple 的问题。
这些内容的来源
这些规则来自 Google 自己的文档,并被转化为机器可执行的检查。97 条规则中的每一条都引用了 Google 发布的一句话,并注明页面、章节和查阅日期;全部 97 条规则都放在一个文件(src/google/docs.ts)中,因此文档变更只需修改一个文件,旁边还有对应的失败测试。资料来源包括 Android 开发者网站、Play Developer API 参考文档、Play Console 帮助中心和 Pub/Sub 文档。
另有 8 条规则附带了来自线上应用的现场观察,注明日期并标记为观察记录,始终放在 Google 的原文旁边,绝不替代原文。这些记录之所以存在,是因为文档说明了应该发生什么,而支持收件箱揭示了实际发生的情况:某个封装层从不显示响应代码、某个插件将基础方案 ID 作为商品 ID 返回、某个 Web 回退流程解析为空,却看起来像是完成了销售。
每条规则都有一个能证明其触发的测试样例,以及 5 个必须完全不产生发现的正确生命周期样例;误报是发布阻断项。如果规则漏掉了你的某起事件,请提交一个附带脱敏时间线的 issue(good first incident);目录就是这样逐步扩充的。
贡献
npm test 会构建并运行所有测试;测试运行器需要 Node 20.9 或更高版本才能使用 --test-concurrency,而工具本身可在 Node 20.0 上运行(已在该版本检查)。有一个文件可以单独运行:node --test tests/rules-B.test.js。添加一条新规则,需要将规则文件放入 src/engine/rules,将对应的 Google 事实加入 src/google/docs.ts(唯一存放 Google 事实的文件,每条都注明日期),提供带有 expected.json 的测试样例,并在 src/engine/catalogue.ts 中添加一行。5 个干净的测试样例必须始终保持零发现;误报是发布阻断项。
许可证和商标
MIT。Google Play 是 Google LLC 的商标。Billing Doctor 是独立项目,与 Google 无关联,也未获得 Google 的认可或赞助。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。