@crvouga/mockingbird-service-kill-bill

中文文档
3.10.16发布于 2026/11/07MITTypeScriptES Module构建来源已验证近 30 天下载 1.6万

Stateful Kill Bill billing emulator with accounts, subscriptions, invoices, payments, credits, retries, catalog, and clock controls.

原始使用文档
文档目录7 节
  1. Install
  2. Usage
  3. Live parity
  4. Controls
  5. API
  6. Fidelity boundary
  7. Account, subscription and invoice compatibility additions

@crvouga/mockingbird-service-kill-bill

Local emulators. Real API contracts. Part of Mockingbird.

Stateful Kill Bill REST emulator for billing integration tests. It models tenant-scoped accounts, payment methods, subscriptions, bundles, invoices, payments, credits, refunds, retries, catalogs, audit metadata, a test clock, account overdue state, and secret-protected lifecycle webhooks.

Install

npm install -D @crvouga/mockingbird-service-kill-bill

ESM only. Node 22+ or Bun 1.2+.

Usage

import { AccountApi, Configuration, apiKey } from "killbill"
import { createServer } from "@crvouga/mockingbird-service-kill-bill/server"

const emulator = await createServer()

// The Kill Bill client your application already uses. Only its basePath changes.
const config = new Configuration({
  basePath: emulator.url,
  username: "admin",
  password: "password",
  apiKey: apiKey("bob", "lazar"),
})
const accounts = new AccountApi(config)
await accounts.createAccount({ name: "Synthetic Customer", externalKey: "customer-1", currency: "USD" }, "example")
const { data: account } = await accounts.getAccountByKey("customer-1")
console.log(account.accountId, account.currency) // acc-… USD

await emulator.close()

The default credentials are Basic auth admin:password plus tenant headers X-Killbill-ApiKey: bob and X-Killbill-ApiSecret: lazar. Mutations also require X-Killbill-CreatedBy; optional reason and comment headers are recorded in the audit journal.

POST /1.0/kb/tenants creates another tenant with Basic auth and X-Killbill-CreatedBy only. Accounts created with that tenant's api key stay hidden from other tenants. POST /1.0/kb/tenants/registerNotificationCallback?cb= stores one push-notification URL for the tenant; invoice and payment events are POSTed there as Kill Bill notification JSON. As in Kill Bill 0.24.16 (measured with a callback against a real server), delivery happens after the request has answered, in order per account: an event raised while the bus is idle waits notificationDelayMs (2000 by default; Kill Bill's bus took 2–3 s), and one raised during or right after a delivery, such as a payment the callback makes, goes out at once (Kill Bill's bus polls again after delivering; ~70 ms measured). GET /1.0/kb/test/queues waits for pending deliveries. A delivery the callback refuses (non-2xx or no answer) is retried 15 minutes, 30 minutes, 2 hours, 12 hours and a day after each failed attempt, when the clock reaches it (a clock move or the next request), then dropped. The in-process onEvent hook still receives events during the request.

GET /1.0/kb/accounts/pagination and GET /1.0/kb/invoices/pagination enumerate the authenticated tenant's existing billing records as bare arrays. offset defaults to 0 and limit to 100; positive limits use creation order, and negative limits reverse it. Invoice updates retain their original position. Responses carry X-Killbill-Pagination-CurrentOffset, TotalNbRecords and MaxNbRecords. NextOffset and NextPageUri are present only when another page exists. Next-page URLs retain the selected namespace and configured admin prefix; continue sending Basic auth and tenant headers. Empty/exhausted pages return []. A zero limit also returns [], as the real SQL pagination does; clients should stop on an empty page. Account balances default to null; use accountWithBalance=true or accountWithBalanceAndCBA=true for the live invoice balance/CBA. These reads use the same collections as invoice creation, payment and voiding, and participate in the standard namespace, clock, reset, snapshot and fault controls.

The oracle is Kill Bill's account resource, invoice resource, pagination response, and ordering implementation. Counts are exact for the emulator's finite collections; the vendor's large-dataset count heuristic, audit projections and search endpoints are unmodelled. Invalid pagination uses the emulator's existing error envelope; exact live error text remains unverified. Invoice pagination returns invoices without their components (amounts zero, no items), as Kill Bill 0.24.16 does.

Live parity

bun run parity -- [scenario…] [--out <dir>] [--allow-remote] replays fixed request scenarios (scripts/scenarios.ts) against a Kill Bill you run and a fresh emulator, each in a new tenant it creates, and diffs the normalized transcripts (ids become placeholders and instants <ts>; dates, amounts and states compare as they are). KILL_BILL_URL names that Kill Bill (for example http://127.0.0.1:8080; KILL_BILL_USERNAME/KILL_BILL_PASSWORD default to admin/password); without it the script exits 2. It writes, so it refuses a non-loopback URL without --allow-remote.

The scenarios cover not-found answers, catalog errors, renewal invoicing (POST /1.0/kb/invoices) and its target-date limits, account credit (committed and draft) and its use, an adjustment of a paid item, seven cancellation shapes, an item-adjusted external refund with its key lookup and audit logs, a future-dated subscription cancelled before it starts, an entitlement pause and resume, a billing pause across two renewals, BCD changes under the test clock (one, two stacked, and on an annual plan), plan changes on annual and semiannual plans (immediate, on a requested day, at the period end, to the same plan, and with a cancellation after it), and a renewal-day move (a billing hold, its release and a BCD change). Plan-change scenarios use a second catalog whose rules leave changes and cancellations at END_OF_TERM and every alignment at Kill Bill's defaults. All of them matched Kill Bill 0.24.16 (the version Geviti runs in production) on 2026-11-06.

Controls

  • POST /__admin/payments/decline-next and /pending-next select the next payment outcome.
  • POST /__admin/payments/:id/retry succeeds the latest failed or pending transaction.
  • POST /__admin/catalog/plans seeds a plan.
  • GET /__admin/billing inspects billing state and audit entries. GET /__admin/state is the shared collection view.
  • GET /1.0/kb/test/clock returns Kill Bill's clock JSON (currentUtcTime, localDate, timeZone). PUT /1.0/kb/test/clock?requestedDate=... advances recurring billing; POST /1.0/kb/test/clock with no requestedDate resets it to the system time.
  • PUT /1.0/kb/subscriptions/:id/undoChangePlan cancels a pending plan change. With no pending change it returns 400 and Kill Bill error code 1071.
  • GET /1.0/kb/test/queues?timeoutSec= returns 200 when no due notification or bus event is outstanding, and 412 after that many seconds when work remains. The default timeout is 5 seconds. A notification counts only when its effectiveDate is at or before the clock; rows in kb_bus_events always count. Both collections are visible through GET /__admin/state.
  • Fault presets cover plugin failure, rate limiting, network loss, and webhook duplicate/reorder/drop delivery.

API

  • KillBillAPI, KillBillAPIOptions, KillBillEvent: REST handler and event contract.
  • Account, subscription, bundle, invoice, payment, transaction, and catalog state types.
  • createRuntime, KillBillRuntime, KillBillRuntimeOptions: full runtime and webhook hub.
  • KILL_BILL_NAMESPACE, KILL_BILL_PRESETS: namespace and fault controls.
  • document, operationIds, supportedOperationIds: generated OpenAPI metadata.
  • createServer, KillBillServerOptions, DEFAULT_PORT, serveTarget from ./server.

Fidelity boundary

The emulator targets deterministic adapter and lifecycle tests. It does not run Kill Bill plugins, tax engines, databases, notification workers, or production entitlement and dunning algorithms. Invoice payments with externalPayment=true are the exception: they are stored on the built-in __EXTERNAL_PAYMENT__ method and are not sent to a gateway. GET /1.0/kb/invoices/{invoiceId}/payments returns those payments with targetInvoiceId. POST /1.0/kb/invoices/{invoiceId}/payments, external or through a payment method, stops at the invoice balance as Kill Bill 0.24.10 does: with nothing owed it records no payment and answers 204 (a retry with the same paymentExternalKey included), and an amount above the balance is refused with PaymentApiException 7199. An account tagged AUTO_INVOICING_OFF (00000000-0000-0000-0000-000000000002) is not invoiced: subscriptions start with chargedThroughDate null, POST /1.0/kb/invoices answers 201 and invoices nothing, and DELETE /1.0/kb/accounts/{id}/tags?tagDef= (204) of that tag bills what was held at once (parity scenario auto-invoicing-off). Automatic payment of a new invoice skips an account tagged AUTO_PAY_OFF (tag definition 00000000-0000-0000-0000-000000000001) and otherwise pays only the balance still owed when it runs, so a payment a notification consumer records before auto-pay runs is not charged again. Auto-pay runs during the request, so a consumer paying on the delayed INVOICE_CREATION finds nothing owed (204), as with Kill Bill 0.24.16. GET /1.0/kb/invoicePayments/{paymentId} returns that payment plus targetInvoiceId. POST /1.0/kb/invoicePayments/{paymentId}/refunds adds a REFUND on the same payment (Kill Bill 0.24.10). externalPayment=true on that refund records a separate credit payment instead, and leaves the original refundedAmount unchanged. GET /1.0/kb/test/queues only reports whether due notifications and bus events, push notifications included, are still outstanding. A future-dated notification, including an end-of-term plan change whose effective date is still ahead of the clock, does not count. GET /1.0/kb/accounts/{accountId}/overdue uses a fixed Kill Bill 0.24.10 config: the synthetic clear state __KILLBILL__CLEAR__OVERDUE_STATE__, and state OD1 once the earliest unpaid invoice (positive balance, not void) is at least one day old (timeSinceEarliestUnpaidInvoiceEqualsOrExceeds). Uploading a replacement overdue config is not modelled. A subscription priceOverrides entry whose phaseName is {planName}-evergreen (or whose phaseType is EVERGREEN when the name is omitted) replaces the catalog amount on the recurring invoice item, and GET /1.0/kb/subscriptions/{id} returns the overrides that were sent. Other catalog phases are not modelled. POST /1.0/kb/subscriptions answers as Kill Bill 0.24.10 does when the catalog cannot serve the plan: with no catalog uploaded (start the emulator with plans: []) it returns 500 java.lang.IllegalStateException "No existing versions in the VersionedCatalog catalog for input date …", so a client that uploads its catalog on that error can recover, and a plan missing from an uploaded catalog returns 400 SubscriptionBaseApiException code 2021 "Could not find any plans named '…'".

Account, subscription and invoice compatibility additions

The contracts below are best guess, not oracle-verified. They follow the official account API, SubscriptionResource, InvoiceResource, Shiro administrator configuration, and external bus listener. The live parity runner exits 2 because it requires an isolated tenant and is not enabled; exact vendor errors, account-specific permissions, proration and billing transitions were not replayed.

  • GET /1.0/kb/security/permissions requires only the configured Basic credentials and returns ["*:*"], modelling the default administrator. Missing or incorrect credentials return 401 UNAUTHORIZED. Custom roles and user-specific permission inventories are not modelled.
  • PUT /1.0/kb/accounts/{accountId} updates only the supplied name and email fields and returns 204. Null leaves a value unchanged unless treatNullAsReset=true. Changed immutable fields return 400 INVALID_ACCOUNT; other update fields return the emulator limitation UNSUPPORTED_ACCOUNT_UPDATE. Rejected updates leave the account unchanged.
  • POST /1.0/kb/subscriptions/{subscriptionId}/block records a blocking state and returns 201 (Kill Bill 0.24.16, parity scenario block-entitlement). The latest effective state per service applies; any blocking service pauses billing, and isBlockEntitlement makes the subscription read BLOCKED. Each state adds PAUSE_/RESUME_ENTITLEMENT and _BILLING events to the subscription and its bundle timeline. requestedDate optionally schedules a state. Once a billing pause takes effect it credits the rest of the billed period (a REPAIR_ADJ invoice whose CBA_ADJ becomes account credit) and the paused renewals bill nothing; a resume bills from its own day to the next billing day, prorated, using that credit first (Kill Bill 0.24.16, parity scenario billing-pause-resume). A blocking state change re-runs invoicing at once, as Kill Bill does.
  • PUT /1.0/kb/subscriptions/{subscriptionId}/bcd accepts billCycleDayLocal from 1 through 31 and returns 204 (Kill Bill 0.24.16, parity scenarios bcd-*). Monthly plans renew in calendar months on the billing day, clamped to month-end. From effectiveFromDate (today by default) the first period off the new day is a stub up to it, billed on the later of the effective date and the last new billing day on or before the stub: billed on a new billing day it is prorated over that new period (BCD 5 from 01-01 bills 02-01..02-05 on 01-05 at 4/31), otherwise over the month from its start (BCD 5 from 02-01 bills it on 02-01 at 4/28). The subscription reports the new day from the first new billing day on. Semiannual and annual plans follow the same rules over their longer periods. Changes stack, each taking over from its own effective date (a later change with the same date replaces it; parity scenario bcd-twice), and a billCycleDayLocal sent when the subscription is created is its BCD from the start. A billing hold from the period end, released on a later day with the BCD moved to that day, renews a full period from it (parity scenario move-renewal-day-annual). Retroactive changes return UNSUPPORTED_BCD.
  • PUT /1.0/kb/subscriptions/{subscriptionId} changes the plan (Kill Bill 0.24.16, parity scenarios change-*). With billingPolicy=IMMEDIATE (the default) the change takes effect today; with only a requestedDate, on that day; with billingPolicy=END_OF_TERM, on the charged-through date, when the renewal bills the new plan. Taking effect, it bills one invoice: the new plan from that day to its next billing day, the period now falling in that day's month (an annual plan changed on 04-11 bills 04-11..next 04-01 at 355/365), and a REPAIR_ADJ for the unused share of the old plan's billed period, then pays the difference as automatic payment would. priceOverrides sent with the change replace the subscription's. Each change is a CHANGE event from the request on (dated the request instant when immediate), prices lists every plan the subscription has had or is changing to, and events report the plan in effect when they happen. Cancelling drops a change scheduled on or after the cancellation; undoChangePlan and DELETE …/changePlan drop any change not yet in effect. The catalog's own change-policy rules are not read: no policy and no date means IMMEDIATE.
  • POST /1.0/kb/invoices/dryRun?accountId=...&targetDate=YYYY-MM-DD previews the recurring periods due through targetDate per active, unblocked subscription without creating invoices, consuming IDs, emitting events, charging payments or advancing billing state. It supports TARGET_DATE only; other dry-run types/actions return an explicit error. A due charge returns 200 with invoice items; nothing due returns 204. Preview IDs are synthetic. Tax and entitlement-change simulations are not modelled.
  • POST /1.0/kb/invoices?accountId=...&targetDate=YYYY-MM-DD invoices every period of the account's active subscriptions that starts on or before targetDate, without moving the clock, pays it as automatic payment would, and returns 201 with the invoice Location. A pending subscription whose start is on or before targetDate is billed too. With nothing to invoice it returns Kill Bill 0.24.16's 404 with no entity (Tomcat's HTML page). A target 37 months or more after today is 400 InvoiceApiException 4005, and more than 15 items for one subscription in a day (previews included) is 400 code 4 "SAFETY BOUND TRIGGERED …" (parity scenarios invoice-renewal, invoice-target-dates).
  • GET /1.0/kb/paymentTransactions?transactionExternalKey=... returns the payment holding that transaction as PaymentJson, or 404 PaymentApiException 7020 "Payment … does not exist".
  • A refund lowers the invoice's refundAdj by the refund, through invoicePayments or payments. With isAdjusted it adds ITEM_ADJ items ("Invoice item adjustment", linked to the requested adjustments) that lower the amount and leave the balance; without it the refund reopens the balance. An unkeyed payment or transaction is keyed by its own id, except the credit payment of an external refund, which gets a generated key (Kill Bill 0.24.16, parity scenario refund-adjusted).
  • POST /1.0/kb/credits adds, per credit, a CREDIT_ADJ (−amount, the given description) and a CBA_ADJ (+amount, "Adjustment (account credit)") on a new invoice, COMMITTED with autoCommit=true and DRAFT otherwise, and answers 200 with the CREDIT_ADJ items (amounts positive). PUT /1.0/kb/invoices/{invoiceId}/commitInvoice commits a DRAFT and returns 204 (parity scenario account-credit-draft). The account's accountCBA is the creditAdj of its committed invoices and its accountBalance the open balance minus that credit. A new invoice uses the credit first ("Adjustment (use of account credit)"), and new credit pays the account's open invoices at once, oldest first, keeping only the rest (parity scenario credit-pays-open-invoice).
  • DELETE /1.0/kb/subscriptions/{id}: entitlement stops at the period end with entitlementPolicy=END_OF_TERM, now with IMMEDIATE, else on requestedDate (a day or an instant) or now; billing stops at the period end with billingPolicy=END_OF_TERM and by default, now with IMMEDIATE, or on requestedDate with useRequestedDateForBilling=true. The subscription reads CANCELLED from the entitlement stop, carries STOP_ENTITLEMENT and STOP_BILLING events, and bills nothing from the billing stop. Billing that stops inside a billed period credits its unused share of what the item is still worth after its item adjustments ("Adjustment (subscription change)"; parity scenario cancel-after-item-adjust): a REPAIR_ADJ invoice whose CBA_ADJ becomes account credit. A requested instant before the subscription started is EntitlementApiException 1001.
  • An unknown account, subscription or invoice id is 404 ObjectDoesNotExist ("Object id=… type=ACCOUNT doesn't exist!"); an unknown account external key is 404 AccountApiException 3003. Kill Bill answers a malformed (non-UUID) id with Tomcat's 404 page; emulator ids are not UUIDs, so the emulator answers every unknown id with ObjectDoesNotExist.
  • audit=FULL (or MINIMAL) on a read adds each invoice, invoice item, payment and transaction's audit rows: INSERT (and UPDATE for payments and transactions) with the writing request's X-Killbill-CreatedBy, -Comment, -Reason, and its X-Request-Id as userToken (generated from the instance's seeded ids when absent). A billing run writes as SubscriptionBaseTransition. Without audit every object carries auditLogs: [].
  • A catalog XML upload with an effectiveDate the tenant already uploaded is 400 CatalogApiException 2080 "Invalid catalog for tenant : "; earlier and later dates load.
  • Subscription, bundle, invoice, invoice item, payment and account reads carry the same fields as Kill Bill 0.24.16 production responses: subscriptions add prices, events, billingPeriod, productName and the other SubscriptionJson fields, report startDate as the creation instant, key an unkeyed account, subscription and bundle by their ids, list an account's invoices by invoice number, report chargedThroughDate null until the first invoice, and read PENDING until a later start (also when cancelled before it). The account invoice list carries amounts and items only with includeInvoiceComponents=true.
  • POST /1.0/kb/invoices/{invoiceId} adjusts the specified invoiceItemId by a positive amount, or its remaining adjustable amount when omitted. It returns 201 and appends a negative ITEM_ADJ linked to the original item, reducing the invoice amount. What it takes beyond the open balance becomes account credit: a CBA_ADJ ("Adjustment (account credit)") that keeps the balance at 0 and raises creditAdj (Kill Bill 0.24.16, parity scenario item-adjust-paid). Invalid items, amounts, accounts or currencies fail without changing the invoice; their exact error codes are emulator approximations.
  • Successful and failed invoice purchases emit INVOICE_PAYMENT_SUCCESS and INVOICE_PAYMENT_FAILED alongside PAYMENT_SUCCESS and PAYMENT_FAILED, with objectType: "INVOICE", objectId equal to the invoice ID, and the account ID. Pending payments emit only PAYMENT_PENDING. Events use both the in-process hook and registered tenant callbacks. POST /1.0/kb/accounts/{accountId}/invoicePayments pays full unpaid invoice balances up to targetDate (today by default), returning 201, or 204 when none are due. Custom paymentAmount and pluginProperty return UNSUPPORTED_INVOICE_PAYMENT. Successful admin retries also emit the invoice success event.

Unknown account, subscription and invoice IDs retain their existing 404 error envelopes; tenant isolation applies to these operations.

版本与发布记录

@crvouga/mockingbird-service-kill-bill 共发布 47 个版本,首次发布于 2026/09/23;过去 12 个月发布了 47 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.10.16latest2026/11/074 天前
3.10.152026/11/074 天前
3.10.142026/11/074 天前
3.10.132026/11/074 天前
3.10.122026/11/065 天前
3.10.112026/11/065 天前

关于 @crvouga/mockingbird-service-kill-bill 的常见问题

@crvouga/mockingbird-service-kill-bill 是什么?

Stateful Kill Bill billing emulator with accounts, subscriptions, invoices, payments, credits, retries, catalog, and clock controls.

如何安装 @crvouga/mockingbird-service-kill-bill?

在项目目录运行 npm install @crvouga/mockingbird-service-kill-bill,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 mockingbird-kill-bill,可以用 npx mockingbird-kill-bill 直接运行。

@crvouga/mockingbird-service-kill-bill 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@crvouga/mockingbird-service-kill-bill 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@crvouga/mockingbird-service-kill-bill 可以商用吗?

@crvouga/mockingbird-service-kill-bill 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@crvouga/mockingbird-service-kill-bill 的下载量有多少?

根据 npm 官方下载统计,@crvouga/mockingbird-service-kill-bill 在 2026/10/11 – 2026/11/09 这 30 天内被下载 16,405 次,日均约 547 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@crvouga/mockingbird-service-kill-bill 还在维护吗?

最新版本 3.10.16 发布于 2026/11/07(4 天前);至今共发布 47 个版本,其中过去 12 个月发布了 47 个;首次发布于 2026/09/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。