内容
# smolbill
[](LICENSE) [](sdk/) [](cmd/smolbill) [](#what-it-does)
**开源、可证明正确的用量计费。单个Go二进制文件和一个Postgres。固定价格,不会收取您收入的百分比。**
smolbill记录用量、生成正确的发票,并可以*证明*——逐行、从原始事件——您的计量器和发票永远不会悄悄地不一致。它通过您已经使用的任何支付处理器收集资金,并且永远不会持有您的资金。AI代理可以通过MCP在纯英语中运行整个系统,而确定性引擎处理每一分钱。
<p align="center">
<img src="assets/demo.gif" alt="smolbill computes a real invoice, then proves the meter and the invoice agree with a hash" width="760">
<br>
<em>一个命令。它计算一个真实的发票,然后<strong>证明</strong>计量器和发票一致——通过哈希。</em>
</p>
---
## 为什么它存在
计费是唯一一个将您的产品转化为货币的系统,也是唯一一个错误的数字不是错误报告的系统——它可能是退款、纠纷、流失的客户或您几个月后发现的悄悄的收入泄漏。大多数计费堆栈都是为了让您永远无法真正*证明*某个发票是正确的而构建的;您需要通过将用量导出到数据仓库并在电子表格中核对来重新检查。通过导出用量到数据仓库并在电子表格中核对。
smolbill基于四个信念构建:
- **正确性是基础,而不是功能。** 金钱是整数精确的十进制数学,而不是浮点数,并且向下舍入以避免多收客户的钱。每个发票都源自事件日志,并带有哈希值,因此计量器和发票可以随时重新派生和比较。漂移是可检测、可量化且响亮的——而不是沉默的。
- **足够小以进行审计,足够小以自行运行。** 一个静态的Go二进制文件和一个Postgres。没有Kafka,没有ClickHouse,没有流处理器,没有工作流引擎,没有数据管道需要照顾。决定客户欠费的东西应该是一个人可以从头到尾阅读和操作的东西,不需要平台团队。
- **您拥有它,并且您拥有自己的资金。** 自行托管、阅读和分叉。它通过您自己的处理器收集资金,并且永远不会成为记录商户,因此它永远不会保留您的资金,处理器冻结也不会使您的计费逻辑崩溃。固定价格——它永远不会收取您收入的百分比。
- **AI消除学习曲线,而不是安全性。** 代理通过MCP操作整个系统,使用纯英语,因此没有API需要学习,也没有文档需要阅读。但是,代理只会传递*意图*——它永远不会进行算术运算,而移动真实货币的少数工具在启用之前保持预览状态。代理无法启用自己。虚构的十进制数可能会结束业务关系,因此模型在结构上远离数学和资金。
## 它做什么
一个单独的静态二进制文件(Postgres或内存中,零设置)为您提供:
- **可证明正确的计量**——幂等事件摄入,带有文档化的去重窗口;延迟和乱序事件正确重新计费,而不是悄悄错误。
- **对账账本**——`GET /v1/reconcile/{invoice}`重新计算发票从实时事件日志,并逐行告诉您是否漂移(200如果一致,409 +差异如果不一致)。`GET /v1/reconcile`对整个账户执行此操作,并报告总风险金额——可证明的收入泄漏,一次调用。
- **收入确认(ASC 606)**——`GET /v1/invoices/{invoice}/revenue?as_of=…`返回确认和递延的分割,沿服务期直线计算,直接从发票计算,因此永远不会漂移。
- **实时强制执行**——`POST /v1/gate`同步回答“这个客户现在可以做这件事吗?”,根据计量的授权(功能+数量)和/或预付钱包(成本)进行计算。 在请求热路径中调用它以**在限制或余额=0时阻止**。支出上限仅警告;门拒绝。
- **从头到尾的AI界面**——您的代理配置和操作计费,通过MCP传递*意图*,并且可以**模拟定价更改对抗实时用量**(`/v1/invoices/simulate`)在提交任何内容之前。确定性引擎处理每一分钱;模型永远不会触及数学。
- **解耦的支付轨道**——仅限发票和收费,处理器无关的接口。smolbill永远不会持有资金或成为记录商户,因此处理器冻结不会使您的计费逻辑崩溃。
- **免费的可嵌入客户门户+预付钱包**——在开源核心中,不是付费附加功能。
- **拒绝感知催收**——失败付款恢复,具有*透明、可配置*的重试节奏(不是黑盒),按拒绝原因路由:软拒绝重试按研究基础的计划;死卡或身份验证挑战立即停止。客户界面文本由您阅读、编辑和预览。
### 快速开始
**在一分钟内看到所有功能 - 无需设置,无需配置,无需帐户:**
```sh
go run ./cmd/smolbill quickstart
```
它会创建一个真实的帐户(计划,用量事件, finalized 发票)并提供服务,然后打印到仪表板,对账账本,实时用量,**时间旅行**(`?as_of=`),和**AI沙盒**(`/simulate`)的链接 - 所有这些都是已填充并可点击的。
然后为真实情况运行:
```sh
# 零设置(内存中,数据不持久):
go run ./cmd/smolbill serve # 监听 :8080
# 使用Postgres(启动时自动应用模式):
DATABASE_URL='postgres://user@localhost:5432/smolbill?sslmode=disable' \
go run ./cmd/smolbill serve
# 或一键运行整个堆栈:
docker compose up
```
### `/v1` 端点
| 方法 | 路径 | 目的 |
|---|---|---|
| `POST` | `/v1/customers` | 创建客户 |
| `POST` | `/v1/meters` | 定义计量器(`count\|sum\|max\|unique`) |
| `POST` | `/v1/plans` | 版本计划 + 价格 |
| `POST` | `/v1/subscriptions` | 将计划附加到客户 |
| `POST` | `/v1/events` | **幂等** 用量摄入(`idempotency_key` 去重) |
| `GET` | `/v1/usage/{customer_id}` | 实时用量 + 投影账单 - 添加 `?as_of=<RFC3339>` **时间旅行** 账单到过去的某个时刻(在延迟事件到达之前) |
| `POST` | `/v1/invoices/preview` | **确定性** 准确发票 + 验证哈希 |
| `POST` | `/v1/invoices/simulate` | **沙盒** - 重放真实用量对抗**提议的**计划,差异 vs 实时账单,不提交任何内容 |
| `POST` | `/v1/invoices/finalize` | 物质化发票 + 持久化对账账本 |
| `GET` | `/v1/reconcile` | **全账户漂移扫描** - 核对每个已完成的发票,报告漂移 + 风险金额 |
| `GET` | `/v1/reconcile/{invoice_id}` | **头条** - 证明一个发票仍然与实时事件日志一致 |
| `GET` | `/v1/invoices/{invoice_id}/verify` | **跨边界** - 证明账本等于支付处理器实际计费的金额 |
| `POST` | `/v1/invoices/{invoice_id}/collect` | **催收** - 现在尝试收款(手动重试,例如在卡更新后) |
| `GET` | `/v1/invoices/{invoice_id}/collection` | 检查恢复状态:每次尝试,拒绝原因,下一次重试时间 |
| `POST` | `/v1/dunning/run` | 处理每个**到期**的收款 - cron 按节奏点击的端点 |
| `GET` | `/v1/analytics` | 账户快照:按货币划分的收入,订阅,催收恢复(实时计算,永远不是缓存计数器) |
| `GET` | `/v1/dunning/templates` | 客户界面催收文本(您的覆盖或默认)- **您可以阅读和编辑** |
| `PUT` | `/v1/dunning/templates/{event}` | 覆盖某事件的文本(验证为渲染后保存) |
| `POST` | `/v1/dunning/preview` | **实时预览** 渲染的催收消息 |
| `POST` | `/v1/entitlements` | 定义功能标志或计量允许 |
| `GET` | `/v1/entitlements/{customer_id}` | 实时限制检查(实时用量,不是受信任的计数器) |
| `POST` | `/v1/alerts` | 注册主动支出提醒(预算的50/80/100%的网络钩) |
| `POST` | `/v1/webhooks` | 注册**签名**的网络钩,用于`invoice.finalized` / `drift.detected`(HMAC-SHA256,秘密返回一次) |
| `GET` | `/v1/webhooks` | 列出已注册的网络钩 |
| `POST` | `/v1/wallet/{customer_id}/topup` | 幂等预付钱包信用 |
| `GET` | `/v1/wallet/{customer_id}` | 钱包余额 + 交易 |
| `GET` | `/dashboard` | 服务器渲染的管理员仪表板(嵌入二进制文件) |
| `GET` | `/dashboard/customers/{id}` | 时间线,用量,发票,钱包的客户 |
| `GET` | `/dashboard/invoices/{id}/reconcile` | 渲染的对账账本 |
| `GET` | `/portal/{customer_id}` | **免费可嵌入客户门户**(用量,账单,钱包) |
| `GET` | `/healthz` | 存活 |
### SDK(TypeScript + Python)
官方的**依赖无关**的客户端在[`sdk/typescript`](sdk/typescript)和[`sdk/python`](sdk/python)。五行代码到一个计量账单:
```ts
const sb = new Smolbill({ baseUrl: "http://localhost:8080" });
const cus = await sb.customers.create({ name: "Acme AI" });
await sb.events.ingest({ idempotency_key: "req-1", customer_id: cus.id, meter_code: "tokens", properties: { n: 1000 } });
```
两者都公开类型化的,签名验证的网络钩处理(`verifyWebhook` / `verify_webhook`)用于签名的`invoice.finalized`和`drift.detected`交付。三个保证让它们诚实:
- **与线路1:1的蛇形大小写** - 没有字段映射层漂移。
- **漂移防护** - [`sdk/routes.json`](sdk/routes.json)是SDK实现的合约,并且Go测试(`TestRoutesMatchSDKManifest`)如果服务器注册了清单中没有列出的路由,则失败,因此新端点不能在客户端赶上之前发货。
- **跨语言签名测试** - Go,TypeScript和Python都断言**相同的金色HMAC摘要**,证明服务器签名的网络钩在每个客户端中完全相同。
`sdk/test-e2e.sh`启动一个真实的二进制文件,并通过两个SDK驱动整个计费生命周期。
### 使用MCP描述您的计费
smolbill从底层构建MCP,而不是在后面添加。连接一个代理(Claude,Cursor)**在一个命令** - `npx`,不是手动编辑路径:
```jsonc
// claude_desktop_config.json
{ "mcpServers": { "smolbill": { "command": "npx", "args": ["-y", "smolbill-mcp"],
"env": { "DATABASE_URL": "postgres://…" } } } } // 省略DATABASE_URL以使用内存
```
然后直接**说出您想要的**,它就会完成:
> *"为我的AI应用设置用量计费 - 0.001美元/令牌,附带20美元的月度基数"*
> *"发票inv_4f…正确吗?请用事件证明"*
> *"如果我将Acme移到新的分级计划,我会收费多少?"*
代理将每个请求映射到正确的工具并显示结果。您一步即可获得一个有效的计划和可预览的发票。
**适用于任何MCP客户端,本地或远程。** 默认的`smolbill mcp`使用**stdio**传输(Claude Code / Desktop,Cursor,Cline,Windsurf,Zed,Continue)。对于托管客户端,`smolbill mcp --http`在`ADDR`的`/mcp`上提供**Streamable HTTP**传输。HTTP传输是API密钥身份验证的(与`/v1`相同),并且服务器通过回声客户端请求的修订版来协商MCP协议版本。
代理可以驱动**所有功能**,不仅仅是设置:
- **配置:** `create_customer`, `create_meter`, `create_plan`, `attach_plan`, `create_entitlement`, `create_webhook`, `set_spend_cap`
- **操作:** `record_usage`, `finalize_invoice`, `topup_wallet`, `collect_invoice`, `run_dunning`
- **观察/证明:** `list_customers`, `list_plans`, `list_subscriptions`, `list_invoices`, `list_meters`, `list_webhooks`, `get_usage`(带有`as_of`时间旅行),`preview_invoice`, `simulate_plan_change`, `reconcile_invoice`, `scan_billing_drift`, `verify_invoice`, `check_entitlement`, `get_wallet`, `get_collection`, `get_analytics`
**没有学习曲线。** 连接时,代理读取smolbill的`instructions`(在MCP `initialize`结果中返回)并调用`get_started`进行定位 - 因此它知道流程,安全规则和您帐户的下一步骤,而您不必阅读这些文档;代理已经这样做了。
**默认情况下安全。** 读取和配置只是发生。移动真实货币的三个工具(`collect_invoice`, `run_dunning`, `topup_wallet`)在启用之前保持**预览**,直到操作员启用引擎(`SMOLBILL_MCP_ARMED=true`)- 代理无法自行启用,因此一个混乱或劫持的代理永远不会自行推送货币,并且有一个可选的每个操作的上限作为后备。并且**没有`charge()`或`calculate_bill()`工具** - 代理传递意图;确定性引擎计算每一分钱。测试断言没有货币数学工具可以公开。
### 仪表板 + 免费客户门户
整个UI是服务器渲染的HTML嵌入二进制文件(没有Next.js,没有构建步骤,没有框架)- 访问`/dashboard`. **客户门户**在`/portal/{id}`显示客户的实时用量,投影账单,钱包余额和授权限制,并带有细致的“由smolbill计量”页脚 - 构建的分发循环,在开源核心中。
### 支付(引入任何处理器)+ 支出提醒
- **任意处理器,一个接口。** `internal/payments`定义一个小的`Processor`接口(`PushInvoice` / `FetchInvoice` / `ChargeInvoice`);引擎仅依赖于它,从不导入一个具体的轨道。适配器适用于**Stripe, Dodo, Paddle, Lemon Squeezy, Polar, Creem, Razorpay, and a non-custodial crypto/stablecoin rail** - 每个都是薄的net/http客户端(金额作为确切的整数小单位发送,idempotency keys on every call),加上一个`fake`测试双倍。
- **选择基于环境驱动的。** 设置`SMOLBILL_PROCESSOR=…`,或仅设置提供商的凭据,smolbill自动检测它。配置了rail后,`finalize`将发票推送到它;未设置,finalize仅限本地(ledger仍然被写入)。smolbill永远不会持有资金或成为您客户的记录商户。
- **商户记录轨道自行催收。** 卡轨道支持会话外重试(smolbill驱动催收)。记录商户轨道和仅限推送的crypto轨道管理它们自己的收款,因此smolbill的`ChargeInvoice`接口返回一个明确的错误,而是将错误路由为卡拒绝。
- **入站网络钩子关闭循环。** 实时卡收集是异步的,因此`POST /integrations/{processor}/webhook`接收轨道的签名事件并通过*相同*催收状态机推进(paid → recovered, failed → next retry, requires-action → pause)。它位于`/v1`之外,因为签名是身份验证,而不是API密钥。Stripe已连接(设置`STRIPE_WEBHOUK_SECRET`); 签名方案由测试验证。
- **添加轨道是一个文件 + 接口中的一行。**
### 对账账本(演示)
发票完成后,询问 `/v1/reconcile/{id}` 是否仍然有效:
```jsonc
// clean - HTTP 200
{ "verdict": "consistent", "hash_match": true, "stored_total": "3.00", "live_total": "3.00" }
// 延迟事件到达后 - HTTP 409
{ "verdict": "drift_detected", "hash_match": false,
"stored_total": "3.00", "live_total": "8.00",
"diffs": ["invoice total 3.00 -> 8.00 (5.00 drift)"],
"lines": [{ "meter_code": "tokens",
"diffs": ["raw event count 1 -> 2 (+1, likely late/out-of-order events)",
"meter value 3000 -> 8000", "amount 3.00 -> 8.00"] }] }
```
计量器和发票永远不会**悄悄**不一致:在finalize时,我们持久化一个账本行/行(原始事件计数,计量器值,行金额,验证哈希);对账重新派生相同的链从当前事件,并显示每个差异。 `GET /v1/reconcile`(无id)在此基础上运行整个已完成的发票,并总结风险金额。
### 底层工作原理
- **货币** (`internal/money`) — 精确的十进制数学运算,不使用浮点数;向下舍入到货币的最小单位,这样我们总是低估账单而不是高估(安全规则)。
- **计量** (`internal/meter`) — 在半开放区间 `[start, end)` 内进行 `count | sum | max | unique` 聚合,以防止相邻区间重复计算边界事件。
- **定价** (`internal/pricing`) — 支持多种定价模式,包括 `flat | per_unit | tiered_graduated | tiered_volume | package | percentage`,每种模式都有可选的**最低消费承诺**和**最高限额**,并严格执行分层验证(错误的计划会立即报错,不会默默地产生错误费率)。
- **税费** — 在客户身上设置一个 `tax_rate`(百分比),然后引擎会在小计上追加一个**可核对的税费行**,该行同样受到校验哈希的保护。smolbill 根据您设置的税率确定性地计算税费行;它不是您的税率解析引擎或税费记录(您可以连接税务服务提供商或商户记录服务来完成此功能)。
- **发票** (`internal/invoice`) — 确定性发票组装,具备时间精确的固定费用分摊(使用量从不分摊),同时生成每行的计量跟踪和 SHA-256 校验哈希。相同的输入 → 相同的发票 → 相同的哈希。这是核对账目的基础。每个最终发票还会从原子序列中获得一个人类可读的、单调递增的编号(`INV-000042`),这是会计和纠纷所需的。
- **数据摄入** (`internal/ingest`) — 在 **已发布、可配置** 的去重窗口内,通过 `idempotency_key` 实现幂等事件接收;晚期或乱序事件被接受并归因于其实际的 `event_time`。
- **存储** (`internal/store`) — 一个接口,两个后端:`memory`(测试、演示)和 `postgres`(pgx,嵌入式模式在连接时应用)。引擎永远不会依赖于具体的数据库。
- **HTTP** (`internal/api`) — 上述 `/v1` 接口,没有 Web 框架(net/http 1.22+ 路由 → 单个二进制文件)。生产环境中已加强安全措施:读写/空闲超时(防止慢速攻击),部署时优雅关闭以耗尽正在进行的请求,panic 恢复以防止一个坏的处理程序使计费服务宕机,并设置请求体大小限制。API 密钥身份验证保护 `/v1` 和 `/mcp`;可选的基于每个密钥的速率限制在数据摄入热路径上。
- **核对** (`internal/reconcile`) — 存储账本与实时重新计算的纯差异;`/v1/reconcile` 背后的证明。
- **支付** (`internal/payments`) — 与处理器无关的支付通道:一个 `Processor` 接口,每个支持的通道都有薄客户端(精确的最小单位,幂等),一个 `fake` 测试替身,和一个 `providers` 注册表,从环境中选择通道。
- **警报** (`internal/alerts`) — 纯粹的 50/80/100% 阈值逻辑 + Webhook 通知器;在每次数据摄入时评估。
- **Webhook** (`internal/webhook`) — 签名(HMAC-SHA256,`X-Smolbill-Signature`)的生命周期事件出站交付;在 finalize 和每次漂移时触发,由协调器捕获,最佳努力交付以防止慢速终端阻塞计费。
- **追讨** (`internal/dunning`) — 纯粹的失败付款恢复状态机:一个可配置的重试调度(默认 +2h/+1d/+3d/+5d/+7d,基于已发布的失败付款研究)和拒绝原因路由(`Classify`),以便硬拒绝和身份验证挑战停止重试而非重复尝试。无需自己的时钟 — 每个转换都经过单元测试。
- **通信** (`internal/comms`) — 品牌化、升级的追讨副本,根据事件渲染。模板**您可以读取、编辑和预览**,渲染后的主题和正文通过每个追讨 Webhook 发送,以便您的 ESP/SMS 直接发送。
- **Web** (`internal/api/web.go` + `templates/`) — 服务器渲染的仪表板、核对视图和可嵌入的客户门户;HTML 通过 `go:embed` 嵌入(单个二进制文件)。
- **引擎** (`internal/engine`) — REST API 和 MCP 服务器共用的计算 + 计划构建,确保两者永远不会产生分歧。
- **MCP** (`internal/mcp`) — 意图のみ MCP 服务器(无 SDK),支持 **stdio** 和 **Streamable HTTP**(API 密钥身份验证,带有安全模型);代理设置规则,从不直接处理金额数学运算。
- **模式** (`migrations/0001_init.sql`) — 完整的 v0 Postgres 数据模型。
## 运行
```sh
go test ./... # 完整测试套件(也可使用:go test -race ./...)
go run ./cmd/smolbill demo # 端到端流程演练(无数据库)
go run ./cmd/smolbill serve # HTTP API
```
`demo` 创建一个客户、一个令牌计量器和一个计划(固定基数 + 渐进令牌定价),摄入使用量(包括被忽略的重复键和在期间开始的计费),并打印一个精确的发票及其计量器→行跟踪和确定性检查。
当设置 `SMOLBILL_TEST_DATABASE_URL` 时,Postgres 集成测试会运行;否则,它们会跳过,因此 `go test ./...` 在没有数据库的情况下仍然通过。
## 设计原则(不可妥协)
1. **AI = 意图,代码 = 数学运算**。代理永远不会计算金额,也不会在无人监督的情况下移动金额。
2. **事件源**。状态是一条可重放的日志;发票始终可以从原始事件中推导出来。
3. **幂等一切**。每个摄入操作都需要一个键;去重窗口有文档记录。
4. **安全失败 = 低估账单,绝不高估**。
5. **核对是产品**,而非事后才想到的功能。
6. **一个二进制文件,一个 Postgres**。简单性是差异化因素。
## 许可证
**引擎采用 AGPLv3 许可证,客户端 SDK 采用 Apache-2.0 许可证。为无法使用 AGPL 的用户提供商业许可证**,详见 [COMMERCIAL.md](COMMERCIAL.md)。
- **引擎** (`cmd/`,`internal/`) — **AGPLv3**。真正的 OSI 开源:自我托管、阅读、分支、交付。唯一的义务:如果您修改它并通过网络提供或分发,您也必须公开您的源代码 — *或* 购买商业许可证。
- **客户端 SDK** (`sdk/python`,`sdk/typescript`,`sdk/mcp`) — **Apache-2.0**,因此您的应用程序可以依赖 smolbill 客户端而没有 copyleft 义务。
- **商业许可证** — 非 AGPL 授权,允许在封闭源代码产品或托管服务中嵌入 smolbill。
### 开源 vs 专业版
| | 开源核心(AGPLv3,免费) | 专业版 / 云(付费) |
|---|---|---|
| 引擎,确定性计费,**核对证明** | ✅ | |
| MCP(stdio + HTTP)+ REST + SDKs | ✅ | |
| 所有支付通道适配器 | ✅ | |
| 仪表板 + 客户门户 + 透明追讨 | ✅ | |
| 收入分析,SSO/RBAC,审计保留 | | ✅ |
| 托管主机,托管 MCP,SLA | | ✅ |
开源核心是真正完整的 — 正确性、核对、追讨、门户和所有支付通道都是免费且可自我托管的。专业版是托管主机和只有作为服务才有意义的功能。欢迎贡献,详见 [CONTRIBUTING.md](CONTRIBUTING.md)。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器