Billing Doctor

io.github.lazytitan30由 GitHub 帳號 lazytitan30 發布v0.1.1更新於 Oct 24, 2026

Diagnose Google Play Billing incidents locally, with evidence. Not affiliated with Google.

概覽

AI 產生的概覽

依據本機時間軸檔案診斷 Google Play Billing 事故,說明購買在哪裡出錯並引用 Google 的規則。

可以做什麼
Billing Doctor 會用一份購買事件的 JSON 時間軸,對照十一組共 97 條規則檢查,輸出發現項目,並附上依據的事件、機制、附日期的 Google 規則原文引用、信心程度與下一步檢查建議。其 MCP 工具為 diagnose_timeline、explain_subscription_state、decode_rtdn、redact_text、list_rules 與 get_rule。它也能驗證時間軸、解碼 Pub/Sub RTDN 訊息、解釋 subscriptionsv2 資源,並從日誌中遮蔽權杖、電子郵件與金鑰。它只讀取與報告,不會在 Google 端做任何變更。
適用情境
當你自行維運 Google Play Billing 後端,而訂閱或購買出現異常時使用,例如已退款的用户仍保有權益,或同一則通知被套用兩次。它適合依據日誌、資料庫紀錄、主控台設定與客服回報排查特定事故,其結束碼可用於 CI 閘門。
執行需求
需要 Node.js 20 或更新版本,透過 npx 或全域 npm 安裝在本機執行。預設離線使用不需要帳號、API 金鑰或環境變數。選用的 --fetch 功能需要你自己的 Google 服務帳戶金鑰檔案,並在 Play 主控台具備查看財務資料權限,另需本機權杖對照檔。
安裝前請注意
預設不發出網路請求,也不傳送遙測資料。選用的 --fetch 模式會用你的服務帳戶金鑰把購買權杖傳送給 Google,因此該金鑰檔案應視為敏感資訊,權杖對照檔也應留在本機。時間軸中應使用化名,而非真實權杖、使用者 ID 或電子郵件;validate 與 redact 指令可協助檢查。付費的 Incident Kit 以檔案形式另行販售,沒有授權伺服器,也不會回傳資料。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Billing Doctor,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

Billing Doctor

Billing Doctor reads what happened to a Google Play purchase and tells you where it broke. It runs on your machine, calls nothing, changes nothing at Google, and shows its evidence. Not affiliated with Google.

It is for developers who run their own Google Play Billing backend, in Node, Python, PHP, Go or Java, with Capacitor, Flutter, React Native or native Android in front, and did not choose a subscription platform. It is the tool you run at 11 pm when a subscription broke.

Twenty seconds

The fixture below ships in the repository, so start from a checkout:

bash
git clone https://github.com/lazytitan30/billing-doctor && cd billing-doctornpx billing-doctor diagnose fixtures/rules/G2-void-then-active.json

(The same fixtures ship inside the npm package: after npm install -g billing-doctor they are under $(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 highexit 1: a high finding is present

Every finding carries the events it rests on, the mechanism, Google's rule quoted with a dated link, a confidence (certain, likely, possible) and the next thing to check. Exit code 1 means a high finding exists, so the command can gate CI.

What it does

You give it a timeline: one JSON file with what happened to one purchase, assembled from your logs, your database, the Cloud Console and the support ticket. The notifications Google sent, the API answers, what your backend wrote, what the user said. Tokens are pseudonyms; nothing real goes in the file.

It checks the timeline against 97 rules in eleven groups. Every one of them rests on a sentence from Google's own documentation, quoted verbatim with the URL and the date it was read:

GroupWhat it catches
A. Configuration and permissionsno notifications at all, 401/403, the topic in another project, negative acks, an unauthenticated endpoint, a release below Billing Library 8 after the 2026-08-31 gate, one-time notifications never switched on
B. Purchase and acknowledgementunacknowledged purchases Google refunded, acknowledging or granting while PENDING, consumables acknowledged not consumed, grants before verification, double grants, acknowledgement failures the client never heard about, prepaid windows, multi-quantity purchases granted as one, plan changes blocked by an unacknowledged subscription, purchases never acknowledged with no refund in the file yet
C. Notificationsthe same message applied twice, out-of-order notifications, writes without re-fetching, revoking on a message with no id, unhandled types, chargeback reviews treated as refunds, test notifications applied, messages never acknowledged and never applied
D. State interpretationexpiry from the wrong source, CANCELED and grace period treated as no access, hold and pause treated as access, EXPIRED still granting, line items ignored, the deprecated v1 resource, test purchases counted, prepaid treated as renewing, tokens used past the sixty-day window
E. Lifecycle eventsRECOVERED with no re-grant, RESTARTED as a new token, DEFERRED with the old expiry, hard-coded grace and hold, scheduled cancellations and scheduled pauses treated as immediate, the deprecated type 8, churn that was really an unaccepted price rise
F. Upgrades and linked tokensthe old token still granting, DEFERRED replacements granted early, re-signups bound to the wrong token, expiry not re-read after proration, out-of-app resubscribes left unlinked, upgrades that drop the obfuscated account id, the prorated-charge mode used for a plan that is cheaper per unit of time
G. Refunds and voided purchasesREVOKED still granting, the void that the state read disagrees with, partial refunds handled as full, no voided-purchases sweep, refunds without revoke, cancel where revoke was meant, refunds of an order that is not the latest
H. Account binding and restoreunmappable notifications, raw account ids, findOne on a shared token, no queryPurchasesAsync on resume, suspended subscriptions not asked for, a different Google account
I. Testing and Consoletest renewals minutes apart, test rows in production, approved but not published, the 12-tester rule, verified but nothing written, tester purchases refunded after three minutes
J. Ledger integritygrants without an idempotency key, tokens kept after expiry, real data in the timeline, expiry in local time, rows keyed on the order id
K. The device and the Play Billing Libraryan empty catalogue, calls made while disconnected, already-owned purchases nobody investigated, recoverable failures never retried, unrecoverable ones retried anyway, DEVELOPER_ERROR, stale product details, duplicate callbacks, overlapping queries, the base plan id used as the product id, purchases that never reached the server, failures reported as success, cancellations treated as errors, the product refused as unavailable at purchase time, restores that rely on the device for consumed purchases after Billing Library 8

billing-doctor rules lists them; billing-doctor rule B1 prints one in full.

Install

Node 20 or later.

bash
npm install -g billing-doctor

Or run it without installing: npx billing-doctor <command>.

Commands

CommandWhat it does
billing-doctor initwrites an empty timeline.json with the policy block filled from a few questions
billing-doctor validate timeline.jsonchecks the file against the schema; warns when a token, user id or email looks real
billing-doctor diagnose timeline.jsonruns every rule; --json for machines, --rules B1,C3 to run some, --kit <path> for the runbook
billing-doctor state sub.jsonexplains a purchases.subscriptionsv2 resource in plain words; --policy timeline.json says where your policy disagrees with Google
billing-doctor rtdn message.jsondecodes a Pub/Sub push body: the type name for the integer, the token, and the next step
billing-doctor redact < logs.txtreplaces purchase tokens with stable tok_ pseudonyms, removes emails, masks order ids, cuts out private keys and bearer tokens
billing-doctor rules and billing-doctor rule <id>the catalogue, and one rule with Google's quote and the runbook entry when the kit is present
billing-doctor mcpserves the same as six MCP tools over stdio

Exit codes: 0 nothing to report, 1 a high finding (or an input that could not be decoded), 2 a usage or file error.

The timeline

docs/timeline-format.md is the contract. Six event kinds: app (what the device reported), api (a call to the Play Developer API and its answer), ledger (what the backend wrote), rtdn (a notification as received), support (what the user said), console (releases and config changes). A policy block says what your backend is supposed to do, so a rule can tell your policy from a bug.

json
{  "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" }  ]}

The fixtures/ folder holds one minimal timeline per rule, three realistic weeks, and five correct lifecycles that must produce nothing. They are the test suite and the best way to learn the format: fixtures/README.md.

With a coding agent

The same engine is an MCP server over stdio, so Claude Code, Codex, Gemini CLI or Cursor can call it while you fix the bug. Tools: diagnose_timeline, explain_subscription_state, decode_rtdn, redact_text, list_rules, get_rule.

Configure it as command npx, args ["-y", "billing-doctor", "mcp"]. For Claude Code, this repository is also a plugin (claude --plugin-dir . from a checkout, with the skill /billing-doctor:diagnose); for Gemini CLI, gemini extensions install https://github.com/lazytitan30/billing-doctor.

The Incident Kit

The free tool is complete on its own. The paid Incident Kit is a folder of files that makes the fix faster: a runbook entry per rule (the symptom as the user reports it, the mechanism, the fix, the regression test to add), a set of realistic incident timelines to replay against your own handler in CI, a TypeScript reference implementation of a verify endpoint, an RTDN handler and a reconciliation job, and checklists for the Play Console, Cloud setup, license testing and the Billing Library 8 and 9 migration. Drop the folder next to the tool as ./billing-doctor-kit (or set BILLING_DOCTOR_KIT) and billing-doctor rule G2 prints the runbook. There is no licence server and no phone-home; the kit is files.

billingdoctor.dev — one runbook page per rule, forty incident timelines, the reference backend, and the checklists.

What is in it, concretely:

Runbookone page per rule: the symptom as a user reports it, the mechanism, Google's rule quoted with the date it was read, the fix, and the regression test to add
Incidentsforty timelines drawn from real failures, each with the findings it produces, plus five correct lifecycles that must stay silent. Replay them against your own handler in CI
Reference backendTypeScript: a verify endpoint with a fail-closed ledger, an RTDN handler with message-id idempotency, an acknowledgement watchdog, a voided-purchases sweep, a reconciliation job, HMAC account binding, a retention purge and a Postgres schema. It passes all forty-five timelines
ChecklistsPlay Console and Cloud setup, license testing, managed publishing, Data Safety, and the Billing Library 8 and 9 migration

Updates for twelve months, as a new download when Google's rules change or a rule is added.

The one optional network feature

diagnose --fetch reads purchases.subscriptionsv2.get for every token in a timeline with your own service-account key and appends Google's answers as api events, so a timeline built from logs gets Google's current view of each purchase. Off by default; nothing goes anywhere but Google.

bash
billing-doctor diagnose timeline.json --fetch --service-account ./key.json --token-map ./tokens.json --out augmented.json

The timeline keeps its pseudonyms: tokens.json maps tok_a1 to the real purchase token and stays on your machine; the appended events carry the pseudonym and the written file never contains a real token. The key needs the "View financial data" permission in the Play Console. Subscriptions only; one-time products are not fetched.

Privacy

No network calls by default. No telemetry, ever. Your logs never leave your machine. The one optional network feature above uses your own key and talks only to Google.

What it is not

Not an SDK, not a hosted service, not a subscription platform, not a dashboard, not legal or financial advice. It never acknowledges, consumes, refunds, revokes, cancels or defers anything. It does not diagnose Apple.

Where this comes from

The rules are Google's own documentation, turned into checks a machine can run. Every one of the 97 quotes a sentence Google published, with the page, the section and the date it was read, and all 97 live in one file (src/google/docs.ts) so a documentation change is a one-file diff with failing tests beside it. The sources are the Android developer site, the Play Developer API reference, the Play Console help centre and the Pub/Sub documentation.

Eight rules also carry a field observation from a live app, dated and marked as an observation, always beside Google's sentence and never in place of it. Those exist because documentation says what should happen and a support inbox says what does: a wrapper that never surfaces the response code, a plugin that returns the base plan id as the product id, a web fallback that resolves with nothing and looks like a sale.

Every rule has a fixture that proves it fires, and five correct lifecycles that must produce no findings at all; a false positive is a release blocker. When a rule misses an incident of yours, open an issue with a redacted timeline (good first incident); that is how the catalogue grows.

Contributing

npm test builds and runs everything; the runner needs Node 20.9 or newer for --test-concurrency, while the tool itself runs on 20.0 (checked there). One file runs alone: node --test tests/rules-B.test.js. A new rule needs its file in src/engine/rules, its Google fact in src/google/docs.ts (the only file with a Google fact, each dated), its fixture with an expected.json, and one line in src/engine/catalogue.ts. The five clean fixtures must stay at zero findings; a false positive is a release blocker.

Licence and trademarks

MIT. Google Play is a trademark of Google LLC. Billing Doctor is an independent project and is not affiliated with, endorsed by, or sponsored by Google.

來源:README.md,提交 bf7e594

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

1
  1. v0.1.1最新Sep 16, 2026

評分

已滿足適用檢查項的 50%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分