@arnaudjnn/billing-tools 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +243 -0
- package/dist/adapters/workos-org.d.ts +80 -0
- package/dist/adapters/workos-org.d.ts.map +1 -0
- package/dist/adapters/workos-org.js +176 -0
- package/dist/adapters/workos-org.js.map +1 -0
- package/dist/agent-auth/claim-store.d.ts +37 -0
- package/dist/agent-auth/claim-store.d.ts.map +1 -0
- package/dist/agent-auth/claim-store.js +64 -0
- package/dist/agent-auth/claim-store.js.map +1 -0
- package/dist/agent-auth/index.d.ts +65 -0
- package/dist/agent-auth/index.d.ts.map +1 -0
- package/dist/agent-auth/index.js +365 -0
- package/dist/agent-auth/index.js.map +1 -0
- package/dist/auth.d.ts +16 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +80 -0
- package/dist/auth.js.map +1 -0
- package/dist/billing.d.ts +59 -0
- package/dist/billing.d.ts.map +1 -0
- package/dist/billing.js +258 -0
- package/dist/billing.js.map +1 -0
- package/dist/cli/client.d.ts +10 -0
- package/dist/cli/client.d.ts.map +1 -0
- package/dist/cli/client.js +22 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/commands.d.ts +4 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +94 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/config.d.ts +16 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +40 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/create-billing.d.ts +78 -0
- package/dist/create-billing.d.ts.map +1 -0
- package/dist/create-billing.js +59 -0
- package/dist/create-billing.js.map +1 -0
- package/dist/dispatch.d.ts +9 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +73 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/events.d.ts +26 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +72 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/invitations.d.ts +56 -0
- package/dist/invitations.d.ts.map +1 -0
- package/dist/invitations.js +117 -0
- package/dist/invitations.js.map +1 -0
- package/dist/machine-payment/index.d.ts +57 -0
- package/dist/machine-payment/index.d.ts.map +1 -0
- package/dist/machine-payment/index.js +117 -0
- package/dist/machine-payment/index.js.map +1 -0
- package/dist/magic-auth.d.ts +4 -0
- package/dist/magic-auth.d.ts.map +1 -0
- package/dist/magic-auth.js +30 -0
- package/dist/magic-auth.js.map +1 -0
- package/dist/mirror.d.ts +35 -0
- package/dist/mirror.d.ts.map +1 -0
- package/dist/mirror.js +63 -0
- package/dist/mirror.js.map +1 -0
- package/dist/plans.d.ts +38 -0
- package/dist/plans.d.ts.map +1 -0
- package/dist/plans.js +132 -0
- package/dist/plans.js.map +1 -0
- package/dist/routes/mcp.d.ts +19 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +39 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/rest.d.ts +20 -0
- package/dist/routes/rest.d.ts.map +1 -0
- package/dist/routes/rest.js +66 -0
- package/dist/routes/rest.js.map +1 -0
- package/dist/routes/webhook.d.ts +8 -0
- package/dist/routes/webhook.d.ts.map +1 -0
- package/dist/routes/webhook.js +39 -0
- package/dist/routes/webhook.js.map +1 -0
- package/dist/sync.d.ts +58 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +202 -0
- package/dist/sync.js.map +1 -0
- package/dist/tools/billing.d.ts +4 -0
- package/dist/tools/billing.d.ts.map +1 -0
- package/dist/tools/billing.js +125 -0
- package/dist/tools/billing.js.map +1 -0
- package/dist/tools/keys.d.ts +4 -0
- package/dist/tools/keys.d.ts.map +1 -0
- package/dist/tools/keys.js +107 -0
- package/dist/tools/keys.js.map +1 -0
- package/dist/tools/register.d.ts +20 -0
- package/dist/tools/register.d.ts.map +1 -0
- package/dist/tools/register.js +87 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +28 -0
- package/dist/types.js.map +1 -0
- package/dist/util/clearout.d.ts +5 -0
- package/dist/util/clearout.d.ts.map +1 -0
- package/dist/util/clearout.js +18 -0
- package/dist/util/clearout.js.map +1 -0
- package/dist/workos.d.ts +6 -0
- package/dist/workos.d.ts.map +1 -0
- package/dist/workos.js +24 -0
- package/dist/workos.js.map +1 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arnaud Jeannin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/arnaudjnn/billing-tools/main/assets/hero.svg" alt="Billing Tools, the get-paid engine for Stripe + WorkOS apps, for humans and AI agents" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<b>Drop-in auth + billing for any Stripe + WorkOS app, usable by humans <i>and</i> AI agents,</b><br/>
|
|
7
|
+
exposed as <b>MCP tools</b>, a <b>REST API</b>, and a <b>CLI</b>, over one storage-agnostic engine.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-635BFF.svg">
|
|
12
|
+
<img alt="TypeScript" src="https://img.shields.io/badge/TypeScript-strict-3178C6.svg">
|
|
13
|
+
<img alt="Stripe" src="https://img.shields.io/badge/Stripe-635BFF.svg">
|
|
14
|
+
<img alt="WorkOS" src="https://img.shields.io/badge/WorkOS-000000.svg">
|
|
15
|
+
<img alt="MCP compatible" src="https://img.shields.io/badge/MCP-compatible-16a34a.svg">
|
|
16
|
+
<img alt="Framework agnostic" src="https://img.shields.io/badge/framework-agnostic-8A8A9A.svg">
|
|
17
|
+
<img alt="PRs welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
**Billing Tools** packages the "get money in" plumbing you'd otherwise rebuild in every SaaS: API-key auth on top of **WorkOS**, token/credit + subscription billing on top of **Stripe**, and (because the next wave of customers is autonomous) first-class **agent** rails: [`auth.md`](https://workos.com/auth-md) self-registration and [`MPP`](https://mpp.dev/) machine payments. Wire it once; serve humans through a browser and agents through headless HTTP with the same engine. Storage is pluggable behind one small adapter (use the built-in WorkOS store, or mirror into your own Postgres).
|
|
23
|
+
|
|
24
|
+
## Table of contents
|
|
25
|
+
|
|
26
|
+
- [Ship tools, not billing plumbing](#ship-tools-not-billing-plumbing)
|
|
27
|
+
- [Key Features](#key-features)
|
|
28
|
+
- [Getting Started](#getting-started)
|
|
29
|
+
- [How it works](#how-it-works)
|
|
30
|
+
- [Agent auth (auth.md)](#agent-auth-authmd)
|
|
31
|
+
- [Machine payments (MPP)](#machine-payments-mpp)
|
|
32
|
+
- [CLI](#cli)
|
|
33
|
+
- [Configuration](#configuration)
|
|
34
|
+
- [Roadmap](#roadmap)
|
|
35
|
+
- [Contributing](#contributing)
|
|
36
|
+
- [License](#license)
|
|
37
|
+
|
|
38
|
+
## Ship tools, not billing plumbing
|
|
39
|
+
|
|
40
|
+
The next generation of software is **agents**, and an agent is only as useful as the **tools** it can call. Tools *are* the product now.
|
|
41
|
+
|
|
42
|
+
But you don't go to war naked. Ship a tool into the wild with no auth and no way to get paid, and you're just donating GPU time to strangers. Someone still has to meter every call, hold a balance, bill the workspace, enforce seats, and cut off the freeloaders: the boring, identical, get-it-wrong-once-and-bleed-money layer that *every* AI product rebuilds from scratch.
|
|
43
|
+
|
|
44
|
+
**Billing Tools is the armor.** Bring a Stripe + WorkOS stack and you inherit, out of the box, the exact monetization model the frontier labs run on. The same shape as **Anthropic, OpenAI, and xAI (Grok)**:
|
|
45
|
+
|
|
46
|
+
- 💰 **A price per token:** every tool call costs tokens, metered and deducted automatically.
|
|
47
|
+
- 🏢 **Workspaces:** the billable account, with one Stripe customer + balance each.
|
|
48
|
+
- 👥 **Users per workspace:** seats, invitations, and roles.
|
|
49
|
+
- 🪙 **Tokens per user:** per-seat grants, seat limits, and auto-reload.
|
|
50
|
+
|
|
51
|
+
So you do the one thing only you can do (**build great tools**) and monetize them the same afternoon. The get-paid layer is already handled, battle-tested, and agent-ready. 🛡️
|
|
52
|
+
|
|
53
|
+
## Key Features
|
|
54
|
+
|
|
55
|
+
### 🔐 Auth & identity
|
|
56
|
+
- 🔑 **API-key auth:** WorkOS organization API keys (`Authorization: Bearer sk_…`), validated on every request.
|
|
57
|
+
- ✉️ **Passwordless magic-auth:** email a 6-digit code, get back a workspace/org + API key. No passwords.
|
|
58
|
+
- 🤖 **auth.md agent self-registration:** the [WorkOS auth.md](https://workos.com/auth-md) protocol (RFC 9728 Protected-Resource-Metadata + RFC 8414 Authorization-Server-Metadata + `/agent/identity` anonymous or verified-email + the claim ceremony), so agents onboard with **no human signup**.
|
|
59
|
+
- ♻️ **RFC 7009 revocation:** revoke a key by value; always-200, no token-existence leak.
|
|
60
|
+
- 🎫 **Optional OAuth-JWT hook:** bring your own MCP OAuth proxy via a single adapter method.
|
|
61
|
+
|
|
62
|
+
### 💳 Billing & payments
|
|
63
|
+
- 🪙 **Usage-metered token wallet:** held in the native Stripe customer credit balance (1 token = 1¢), with idempotent credit/debit.
|
|
64
|
+
- 🛒 **Checkout top-ups:** Stripe Checkout that auto-offers cards **+ Apple Pay / Google Pay / Link**.
|
|
65
|
+
- 📦 **Declarative subscription plans:** describe plans in code; products/prices are **auto-provisioned in Stripe** via `lookup_key` (immutable-price safe, orphan-cleaning). Zero dashboard clicks.
|
|
66
|
+
- 🎟️ **Per-seat / per-cycle token grants + seat limits:** included tokens scale with active members.
|
|
67
|
+
- 🔁 **Auto-reload:** off-session saved-card recharge when the balance drops below a threshold.
|
|
68
|
+
- 🧾 **Invoices + 🏛️ Customer Portal:** list invoices, and a one-call Stripe Billing Portal URL for self-serve upgrade/downgrade/cancel + card updates.
|
|
69
|
+
- 🔒 **Idempotency on every money move:** welcome bonus, checkout credit, and per-cycle grants each carry a stable key, so retries/replays never double-charge.
|
|
70
|
+
|
|
71
|
+
### 🤖 Built for AI agents
|
|
72
|
+
- 📄 **auth.md:** the [agent onboarding standard](https://workos.com/auth-md), served for you (`/auth.md` narrative + discovery metadata).
|
|
73
|
+
- 🏧 **MPP machine payments:** Stripe's [Machine Payments Protocol](https://mpp.dev/): a HTTP **402 + `WWW-Authenticate: Payment`** challenge for pay-per-request (SPT card or crypto/USDC), the payment sibling of auth.md's 401.
|
|
74
|
+
- 🧰 **MCP server tools:** `get_api_key`, `get_token_balance`, `buy_tokens`, `list_plans`, `get_billing_portal`, and more, drop straight into Claude, Cursor, or any MCP client.
|
|
75
|
+
- 🧭 **Discovery hints:** every 401/402 advertises `resource_metadata`, so an agent can bootstrap unattended.
|
|
76
|
+
|
|
77
|
+
### 🧩 Three surfaces, one engine
|
|
78
|
+
- 🛠️ **MCP:** `createMcpTransport({ register, adapter })`.
|
|
79
|
+
- 🌐 **REST:** `createToolListHandler` + `createToolDispatchHandler` (429/401/400/404 mapping built in).
|
|
80
|
+
- ⌨️ **CLI:** `registerBillingCommands(program, …)` gives you `auth`, `keys`, `balance`, `buy`, `invoices`.
|
|
81
|
+
- 🪝 **Stripe webhook:** `createStripeWebhookHandler()` for instant checkout crediting.
|
|
82
|
+
|
|
83
|
+
### 🔄 Zero-webhook sync
|
|
84
|
+
- 📡 **Event polling:** reconcile Stripe **and** WorkOS via their Events APIs. No webhook endpoints, no signing secrets.
|
|
85
|
+
- ⏱️ **Any scheduler:** run it in-process with `sync.start()` (persistent hosts) or as a serverless cron via `createSyncRoute()`.
|
|
86
|
+
- 🪞 **Generic DB mirror:** shadow WorkOS orgs/users into your own tables (`createMirror`), driver-agnostic.
|
|
87
|
+
- 📉 **Dunning hook:** `invoice.payment_failed` flips the org to `past_due` and fires an `onPaymentFailed` hook (Stripe Smart Retries do the retries).
|
|
88
|
+
|
|
89
|
+
### 🌍 Framework and platform agnostic
|
|
90
|
+
- ⚡ **Web-standard handlers:** everything returns `Request` to `Response`; mount in Next.js, Hono, Bun, Deno, or Cloudflare Workers.
|
|
91
|
+
- 🧱 **No framework imports:** runtime deps are just `stripe` + `@workos-inc/node`.
|
|
92
|
+
- 🗄️ **Bring your own DB:** no `pg` dependency; you pass a `query` executor (any Postgres-compatible driver).
|
|
93
|
+
- 🔌 **Pluggable storage adapter:** WorkOS-only, or WorkOS + your DB mirror, behind one interface.
|
|
94
|
+
- ☁️ **No lock-in:** runs on any Node host (Railway / Render / Fly / Vercel / self-host).
|
|
95
|
+
|
|
96
|
+
### 🏛️ Design doctrine
|
|
97
|
+
- 📐 **SDK-first:** thin-wraps the Stripe & WorkOS SDKs (SDK types, pagination, typed errors, idempotency) so the lib evolves *with* the platforms instead of drifting.
|
|
98
|
+
- 🎯 **One memoized client per SDK:** lazy, never constructed at import.
|
|
99
|
+
- 🔒 **Secure by default:** AES-256-GCM session encryption at rest, SHA-256-hashed claim tokens, verified-domain-only internal-org checks.
|
|
100
|
+
- 🧪 **Offline-testable:** the auth.md + MPP protocol surfaces are pure `Request` to `Response`, unit-testable without a live account.
|
|
101
|
+
|
|
102
|
+
## Getting Started
|
|
103
|
+
|
|
104
|
+
### Prerequisites
|
|
105
|
+
- **Node 18+**
|
|
106
|
+
- A **Stripe** secret key (`STRIPE_SECRET_KEY`)
|
|
107
|
+
- A **WorkOS** API key + client id (`WORKOS_API_KEY`, `WORKOS_CLIENT_ID`)
|
|
108
|
+
|
|
109
|
+
### Install
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm install @arnaudjnn/billing-tools
|
|
113
|
+
# peers (provide the ones you use): @modelcontextprotocol/sdk mcp-handler zod
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Ships compiled `dist/`, so there's no build step or `transpilePackages` needed in the consumer.
|
|
117
|
+
|
|
118
|
+
### Environment
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
STRIPE_SECRET_KEY=sk_test_…
|
|
122
|
+
STRIPE_WEBHOOK_SECRET=whsec_… # only if you mount the webhook
|
|
123
|
+
WORKOS_API_KEY=sk_…
|
|
124
|
+
WORKOS_CLIENT_ID=client_…
|
|
125
|
+
INTERNAL_ORG_DOMAINS=acme.com # optional: orgs with these verified domains are unmetered
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Wire it up (one call)
|
|
129
|
+
|
|
130
|
+
`createBilling()` composes every surface from a single config (one module instance = one shared auth context). You supply the adapter + config; it returns the mounted handlers.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// billing.ts
|
|
134
|
+
import { createBilling } from "@arnaudjnn/billing-tools";
|
|
135
|
+
import { WorkOSOrgAdapter } from "@arnaudjnn/billing-tools/adapters/workos-org";
|
|
136
|
+
|
|
137
|
+
export const { mcp, restList, restDispatch, webhook, agentAuth } = createBilling({
|
|
138
|
+
adapter: new WorkOSOrgAdapter(), // WorkOS is the source of truth
|
|
139
|
+
config: { currency: "usd", baseUrl: process.env.APP_URL! },
|
|
140
|
+
agentAuth: { branding: { productName: "Acme" } }, // enables auth.md (optional)
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// app/[transport]/route.ts → export const { GET, POST } = mcp
|
|
146
|
+
// app/api/v0/route.ts → export const GET = restList
|
|
147
|
+
// app/api/v0/[tool]/route.ts → export const POST = restDispatch
|
|
148
|
+
// app/api/stripe/webhook/route.ts→ export const POST = webhook
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Prefer fine-grained control? Every factory is exported individually (`registerBillingTools`, `createMcpTransport`, `createToolListHandler`, `createToolDispatchHandler`, `createStripeWebhookHandler`, `createAgentAuth`, …) so you can build your own composition root.
|
|
152
|
+
|
|
153
|
+
### First call
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# List available tools + costs
|
|
157
|
+
curl https://your-app.com/api/v0
|
|
158
|
+
|
|
159
|
+
# Use a key (Bearer sk_…)
|
|
160
|
+
curl -X POST https://your-app.com/api/v0/get_token_balance \
|
|
161
|
+
-H "Authorization: Bearer sk_…"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Add agent onboarding with [`createAgentAuth`](#agent-auth-authmd) and pay-per-call with [`createMachinePaymentHandler`](#machine-payments-mpp). See below.
|
|
165
|
+
|
|
166
|
+
## How it works
|
|
167
|
+
|
|
168
|
+
**WorkOS is always the source of truth.** Your app's `orgId` is opaque: implement the adapter and everything (auth, metering, Stripe math, all surfaces) works unchanged. Two shipped patterns:
|
|
169
|
+
|
|
170
|
+
| Pattern | What `orgId` maps to | Storage | Use when |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
| **A: WorkOS-only** | the WorkOS org id | none beyond WorkOS | you don't have (or need) your own DB rows |
|
|
173
|
+
| **B: WorkOS + DB mirror** | your own id (e.g. `ws_…`) | a `workos_org_id` column + `org.externalId`, 1:1 | you keep local rows and mirror WorkOS into them |
|
|
174
|
+
|
|
175
|
+
In both, API keys are WorkOS org keys and the Stripe pointer lives on the org. The `WorkOSOrgAdapter` (`@arnaudjnn/billing-tools/adapters/workos-org`) implements both; pass a `map` for Pattern B.
|
|
176
|
+
|
|
177
|
+
## Agent auth (auth.md)
|
|
178
|
+
|
|
179
|
+
Implements the [WorkOS auth.md](https://workos.com/auth-md) spec.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { createAgentAuth } from "@arnaudjnn/billing-tools";
|
|
183
|
+
|
|
184
|
+
export const agentAuth = createAgentAuth({
|
|
185
|
+
adapter, config,
|
|
186
|
+
branding: { productName: "Acme", logoUri: "https://acme.com/logo.svg" },
|
|
187
|
+
identityTypes: ["anonymous", "verified_email"],
|
|
188
|
+
});
|
|
189
|
+
// mount: /auth.md, /.well-known/oauth-{protected-resource,authorization-server},
|
|
190
|
+
// /agent/identity, /agent/identity/claim, /oauth/token, /oauth/revoke
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
An agent hits a 401, follows the `resource_metadata` hint to your metadata, reads `/auth.md`, then registers (either **anonymously** for an instant key, or via a **verified-email** claim ceremony where the user reads back a code) and starts calling tools. All handlers are `Request` to `Response`.
|
|
194
|
+
|
|
195
|
+
## Machine payments (MPP)
|
|
196
|
+
|
|
197
|
+
Implements Stripe's [Machine Payments Protocol](https://mpp.dev/).
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { createMachinePaymentHandler } from "@arnaudjnn/billing-tools";
|
|
201
|
+
|
|
202
|
+
const pay = createMachinePaymentHandler({ methods: ["stripe"], amount: 50, currency: "usd" });
|
|
203
|
+
const gate = await pay.requirePayment(request);
|
|
204
|
+
if (gate instanceof Response) return gate; // 402 + WWW-Authenticate: Payment challenge
|
|
205
|
+
// else settlement succeeded, serve the paid resource
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The 402 challenge + credential parsing ship ready and are offline-testable. Actual settlement (card via shared payment tokens, or crypto/USDC) is injected via a `settle` function once your Stripe account is enabled for machine payments; until then the gate returns a clean "settlement not enabled" 402 (never a crash). `createPaymentMd()` serves an agent-facing `/payment.md`.
|
|
209
|
+
|
|
210
|
+
## CLI
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { registerBillingCommands } from "@arnaudjnn/billing-tools";
|
|
214
|
+
import { Command } from "commander";
|
|
215
|
+
|
|
216
|
+
const program = new Command();
|
|
217
|
+
registerBillingCommands(program, { configDir: "~/.acme", envPrefix: "ACME", defaultUrl: "https://acme.com" });
|
|
218
|
+
// acme auth | keys list|revoke | balance | buy | invoices
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Configuration
|
|
222
|
+
|
|
223
|
+
`BillingConfig` (pass to `resolveConfig`):
|
|
224
|
+
|
|
225
|
+
| Field | Default | Purpose |
|
|
226
|
+
|---|---|---|
|
|
227
|
+
| `baseUrl` | required | Checkout success/cancel + portal return URLs |
|
|
228
|
+
| `currency` | `"usd"` | Stripe currency |
|
|
229
|
+
| `freeTokens` | `100` | Welcome credit on first customer creation |
|
|
230
|
+
| `internalDomains` | `[]` | Orgs with these **verified** WorkOS domains are unmetered (see `internalDomainsFromEnv`) |
|
|
231
|
+
|
|
232
|
+
## Roadmap
|
|
233
|
+
|
|
234
|
+
- 🧾 Stripe Tax (`automatic_tax`) opt-in
|
|
235
|
+
- 🪙 x402 machine-payment protocol alongside [MPP](https://mpp.dev/)
|
|
236
|
+
|
|
237
|
+
## Contributing
|
|
238
|
+
|
|
239
|
+
Issues and PRs welcome. The engine is plain TypeScript compiled with `tsc`; `dist/` is committed (consumers install via Git). See `AGENTS.md` for the architecture, the adapter interface, the SDK-first doctrine, and the release flow.
|
|
240
|
+
|
|
241
|
+
## License
|
|
242
|
+
|
|
243
|
+
[MIT](LICENSE) © Arnaud Jeannin
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { BillingAdapter, BillingUser, ApiKeyInfo } from "../types.js";
|
|
2
|
+
/** Bidirectional id map between the app's `orgId` and the WorkOS org id. */
|
|
3
|
+
export interface WorkOSOrgMap {
|
|
4
|
+
/** app orgId → WorkOS org id (create/reconcile-on-read as needed). */
|
|
5
|
+
toWorkosOrgId(orgId: string): Promise<string>;
|
|
6
|
+
/** WorkOS org id → app orgId, or null if it maps to nothing. */
|
|
7
|
+
toOrgId(workosOrgId: string): Promise<string | null>;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkOSOrgAdapterOptions {
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
clientId?: string;
|
|
12
|
+
/** Key name for new keys. Default "API Key". */
|
|
13
|
+
keyName?: string;
|
|
14
|
+
/** DB-mirror id map. Omit for a WorkOS-only app (identity mapping). */
|
|
15
|
+
map?: WorkOSOrgMap;
|
|
16
|
+
/** Override org creation (e.g. also insert the mirror workspace row +
|
|
17
|
+
* membership). Omit to use the default (company-domain org + verified
|
|
18
|
+
* domain + membership). */
|
|
19
|
+
ensureOrg?: (user: BillingUser) => Promise<{
|
|
20
|
+
orgId: string;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export declare class WorkOSOrgAdapter implements BillingAdapter {
|
|
24
|
+
private apiKey?;
|
|
25
|
+
private clientId?;
|
|
26
|
+
private map?;
|
|
27
|
+
private ensureOrg?;
|
|
28
|
+
constructor(opts?: WorkOSOrgAdapterOptions);
|
|
29
|
+
private get workos();
|
|
30
|
+
/** app orgId → WorkOS org id (identity when no map is configured). */
|
|
31
|
+
private wid;
|
|
32
|
+
validateApiKey(token: string): Promise<{
|
|
33
|
+
orgId: string;
|
|
34
|
+
} | null>;
|
|
35
|
+
getOrgDomains(orgId: string): Promise<string[]>;
|
|
36
|
+
getBillingCustomerId(orgId: string): Promise<string | null>;
|
|
37
|
+
setBillingCustomerId(orgId: string, customerId: string): Promise<void>;
|
|
38
|
+
ensureOrgForUser(user: BillingUser): Promise<{
|
|
39
|
+
orgId: string;
|
|
40
|
+
}>;
|
|
41
|
+
mintApiKey(orgId: string, name: string, _createdBy?: string): Promise<{
|
|
42
|
+
id: string;
|
|
43
|
+
value: string;
|
|
44
|
+
}>;
|
|
45
|
+
listApiKeys(orgId: string): Promise<ApiKeyInfo[]>;
|
|
46
|
+
/** Revoke by raw token value (RFC 7009): validate → key id → hard delete. */
|
|
47
|
+
revokeApiKeyByToken(token: string): Promise<boolean>;
|
|
48
|
+
/** Create an org with no user (auth.md anonymous). No verified domain, so it
|
|
49
|
+
* never satisfies the internal-org unmetered check. Returns the WorkOS org id
|
|
50
|
+
* as `orgId` (Pattern A). With a `map` configured there's no mirror row, so
|
|
51
|
+
* prefer disabling anonymous for mirror apps (identityTypes without it). */
|
|
52
|
+
createAnonymousOrg(opts: {
|
|
53
|
+
name: string;
|
|
54
|
+
metadata?: Record<string, string>;
|
|
55
|
+
}): Promise<{
|
|
56
|
+
orgId: string;
|
|
57
|
+
}>;
|
|
58
|
+
revokeApiKey(orgId: string, id: string): Promise<{
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
} | null>;
|
|
62
|
+
/** Active-member count for the org (per-seat token grants + seat limits).
|
|
63
|
+
* Auto-paginates so orgs with >100 members aren't undercounted. */
|
|
64
|
+
memberCount(orgId: string): Promise<number>;
|
|
65
|
+
getSubscription(orgId: string): Promise<{
|
|
66
|
+
plan: string | null;
|
|
67
|
+
status: string | null;
|
|
68
|
+
subscriptionId: string | null;
|
|
69
|
+
periodEnd: string | null;
|
|
70
|
+
}>;
|
|
71
|
+
/** Write subscription state onto the org metadata. `plan: undefined` leaves
|
|
72
|
+
* the plan as-is; `null` clears it (back to the default plan). */
|
|
73
|
+
setSubscription(orgId: string, sub: {
|
|
74
|
+
plan?: string | null;
|
|
75
|
+
status: string | null;
|
|
76
|
+
subscriptionId: string | null;
|
|
77
|
+
periodEnd: string | null;
|
|
78
|
+
}): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=workos-org.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workos-org.d.ts","sourceRoot":"","sources":["../../src/adapters/workos-org.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { DomainDataState, OrganizationDomainState } from "@workos-inc/node";
|
|
2
|
+
import { getWorkOS } from "../workos.js";
|
|
3
|
+
import { lookupCompany } from "../util/clearout.js";
|
|
4
|
+
export class WorkOSOrgAdapter {
|
|
5
|
+
apiKey;
|
|
6
|
+
clientId;
|
|
7
|
+
map;
|
|
8
|
+
ensureOrg;
|
|
9
|
+
constructor(opts = {}) {
|
|
10
|
+
this.apiKey = opts.apiKey;
|
|
11
|
+
this.clientId = opts.clientId;
|
|
12
|
+
this.map = opts.map;
|
|
13
|
+
this.ensureOrg = opts.ensureOrg;
|
|
14
|
+
}
|
|
15
|
+
// The shared, lazily-memoized client (see workos.ts). With no explicit creds
|
|
16
|
+
// this is the process-wide singleton; explicit creds get their own client.
|
|
17
|
+
get workos() {
|
|
18
|
+
return getWorkOS({ apiKey: this.apiKey, clientId: this.clientId });
|
|
19
|
+
}
|
|
20
|
+
/** app orgId → WorkOS org id (identity when no map is configured). */
|
|
21
|
+
wid(orgId) {
|
|
22
|
+
return this.map ? this.map.toWorkosOrgId(orgId) : Promise.resolve(orgId);
|
|
23
|
+
}
|
|
24
|
+
async validateApiKey(token) {
|
|
25
|
+
try {
|
|
26
|
+
const { apiKey } = await this.workos.apiKeys.createValidation({ value: token });
|
|
27
|
+
if (!apiKey)
|
|
28
|
+
return null;
|
|
29
|
+
const workosOrgId = apiKey.owner.id;
|
|
30
|
+
const orgId = this.map ? await this.map.toOrgId(workosOrgId) : workosOrgId;
|
|
31
|
+
return orgId ? { orgId } : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async getOrgDomains(orgId) {
|
|
38
|
+
const org = await this.workos.organizations.getOrganization(await this.wid(orgId));
|
|
39
|
+
// Only VERIFIED domains count — a pending/failed domain must not unlock the
|
|
40
|
+
// internal-org unmetered path (see auth.ts isInternalOrg).
|
|
41
|
+
return org.domains
|
|
42
|
+
.filter((d) => d.state === OrganizationDomainState.Verified)
|
|
43
|
+
.map((d) => d.domain);
|
|
44
|
+
}
|
|
45
|
+
async getBillingCustomerId(orgId) {
|
|
46
|
+
const org = await this.workos.organizations.getOrganization(await this.wid(orgId));
|
|
47
|
+
// Native field first (v10); fall back to the legacy metadata pointer so
|
|
48
|
+
// apps that stored it in metadata under v8 don't orphan their customer.
|
|
49
|
+
return (org.stripeCustomerId ||
|
|
50
|
+
org.metadata?.stripeCustomerId ||
|
|
51
|
+
null);
|
|
52
|
+
}
|
|
53
|
+
async setBillingCustomerId(orgId, customerId) {
|
|
54
|
+
await this.workos.organizations.updateOrganization({
|
|
55
|
+
organization: await this.wid(orgId),
|
|
56
|
+
stripeCustomerId: customerId,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async ensureOrgForUser(user) {
|
|
60
|
+
if (this.ensureOrg)
|
|
61
|
+
return this.ensureOrg(user);
|
|
62
|
+
const memberships = await this.workos.userManagement.listOrganizationMemberships({ userId: user.id });
|
|
63
|
+
if (memberships.data.length > 0)
|
|
64
|
+
return { orgId: memberships.data[0].organizationId };
|
|
65
|
+
const domain = user.email.split("@")[1];
|
|
66
|
+
const company = await lookupCompany(domain);
|
|
67
|
+
const org = await this.workos.organizations.createOrganization({
|
|
68
|
+
name: company?.name || domain,
|
|
69
|
+
domainData: [{ domain, state: DomainDataState.Verified }],
|
|
70
|
+
metadata: company?.logoUrl ? { logoUrl: company.logoUrl } : undefined,
|
|
71
|
+
});
|
|
72
|
+
await this.workos.userManagement.createOrganizationMembership({
|
|
73
|
+
organizationId: org.id,
|
|
74
|
+
userId: user.id,
|
|
75
|
+
});
|
|
76
|
+
return { orgId: org.id };
|
|
77
|
+
}
|
|
78
|
+
async mintApiKey(orgId, name, _createdBy) {
|
|
79
|
+
const key = await this.workos.apiKeys.createOrganizationApiKey({
|
|
80
|
+
organizationId: await this.wid(orgId),
|
|
81
|
+
name,
|
|
82
|
+
});
|
|
83
|
+
return { id: key.id, value: key.value };
|
|
84
|
+
}
|
|
85
|
+
async listApiKeys(orgId) {
|
|
86
|
+
const paginatable = await this.workos.apiKeys.listOrganizationApiKeys({
|
|
87
|
+
organizationId: await this.wid(orgId),
|
|
88
|
+
});
|
|
89
|
+
const keys = await paginatable.autoPagination();
|
|
90
|
+
return keys.map((k) => ({
|
|
91
|
+
id: k.id,
|
|
92
|
+
name: k.name,
|
|
93
|
+
obfuscatedValue: k.obfuscatedValue,
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
/** Revoke by raw token value (RFC 7009): validate → key id → hard delete. */
|
|
97
|
+
async revokeApiKeyByToken(token) {
|
|
98
|
+
try {
|
|
99
|
+
const { apiKey } = await this.workos.apiKeys.createValidation({ value: token });
|
|
100
|
+
if (!apiKey)
|
|
101
|
+
return false;
|
|
102
|
+
await this.workos.apiKeys.deleteApiKey(apiKey.id);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Create an org with no user (auth.md anonymous). No verified domain, so it
|
|
110
|
+
* never satisfies the internal-org unmetered check. Returns the WorkOS org id
|
|
111
|
+
* as `orgId` (Pattern A). With a `map` configured there's no mirror row, so
|
|
112
|
+
* prefer disabling anonymous for mirror apps (identityTypes without it). */
|
|
113
|
+
async createAnonymousOrg(opts) {
|
|
114
|
+
const org = await this.workos.organizations.createOrganization({
|
|
115
|
+
name: opts.name,
|
|
116
|
+
metadata: opts.metadata,
|
|
117
|
+
});
|
|
118
|
+
const orgId = this.map ? (await this.map.toOrgId(org.id)) ?? org.id : org.id;
|
|
119
|
+
return { orgId };
|
|
120
|
+
}
|
|
121
|
+
async revokeApiKey(orgId, id) {
|
|
122
|
+
// Full-list scope check (all pages) so a key on a later page isn't mistaken
|
|
123
|
+
// for "not found"; the SDK delete needs only the id.
|
|
124
|
+
const paginatable = await this.workos.apiKeys.listOrganizationApiKeys({
|
|
125
|
+
organizationId: await this.wid(orgId),
|
|
126
|
+
});
|
|
127
|
+
const keys = await paginatable.autoPagination();
|
|
128
|
+
const target = keys.find((k) => k.id === id);
|
|
129
|
+
if (!target)
|
|
130
|
+
return null;
|
|
131
|
+
await this.workos.apiKeys.deleteApiKey(id);
|
|
132
|
+
return { id: target.id, name: target.name };
|
|
133
|
+
}
|
|
134
|
+
// ── Subscription state + seats (org metadata; used by the billing-sync) ────
|
|
135
|
+
/** Active-member count for the org (per-seat token grants + seat limits).
|
|
136
|
+
* Auto-paginates so orgs with >100 members aren't undercounted. */
|
|
137
|
+
async memberCount(orgId) {
|
|
138
|
+
const paginatable = await this.workos.userManagement.listOrganizationMemberships({
|
|
139
|
+
organizationId: await this.wid(orgId),
|
|
140
|
+
statuses: ["active"],
|
|
141
|
+
});
|
|
142
|
+
const members = await paginatable.autoPagination();
|
|
143
|
+
return members.length;
|
|
144
|
+
}
|
|
145
|
+
async getSubscription(orgId) {
|
|
146
|
+
const org = await this.workos.organizations.getOrganization(await this.wid(orgId));
|
|
147
|
+
const m = (org.metadata ?? {});
|
|
148
|
+
return {
|
|
149
|
+
plan: m.plan ?? null,
|
|
150
|
+
status: m.subscriptionStatus ?? null,
|
|
151
|
+
subscriptionId: m.stripeSubscriptionId ?? null,
|
|
152
|
+
periodEnd: m.subscriptionPeriodEnd ?? null,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** Write subscription state onto the org metadata. `plan: undefined` leaves
|
|
156
|
+
* the plan as-is; `null` clears it (back to the default plan). */
|
|
157
|
+
async setSubscription(orgId, sub) {
|
|
158
|
+
const wid = await this.wid(orgId);
|
|
159
|
+
const org = await this.workos.organizations.getOrganization(wid);
|
|
160
|
+
const metadata = { ...(org.metadata ?? {}) };
|
|
161
|
+
const set = (k, v) => {
|
|
162
|
+
if (v === undefined)
|
|
163
|
+
return;
|
|
164
|
+
if (v === null || v === "")
|
|
165
|
+
delete metadata[k];
|
|
166
|
+
else
|
|
167
|
+
metadata[k] = v;
|
|
168
|
+
};
|
|
169
|
+
set("subscriptionStatus", sub.status);
|
|
170
|
+
set("stripeSubscriptionId", sub.subscriptionId);
|
|
171
|
+
set("subscriptionPeriodEnd", sub.periodEnd);
|
|
172
|
+
set("plan", sub.plan);
|
|
173
|
+
await this.workos.organizations.updateOrganization({ organization: wid, metadata });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=workos-org.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workos-org.js","sourceRoot":"","sources":["../../src/adapters/workos-org.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type ClaimStatus = "pending" | "ready" | "error";
|
|
2
|
+
export type ClaimReadResult = {
|
|
3
|
+
status: "not_found";
|
|
4
|
+
} | {
|
|
5
|
+
status: "pending";
|
|
6
|
+
email: string;
|
|
7
|
+
} | {
|
|
8
|
+
status: "ready";
|
|
9
|
+
email: string;
|
|
10
|
+
apiKey: string;
|
|
11
|
+
organizationId: string;
|
|
12
|
+
} | {
|
|
13
|
+
status: "error";
|
|
14
|
+
email: string;
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
/** Persistence for the claim ceremony. claim_token is hashed at rest by the
|
|
18
|
+
* default store (SHA-256) per the auth.md spec; a custom store should do the
|
|
19
|
+
* same and never persist the plaintext token. */
|
|
20
|
+
export interface ClaimStore {
|
|
21
|
+
create(email: string): Promise<{
|
|
22
|
+
claimToken: string;
|
|
23
|
+
expiresIn: number;
|
|
24
|
+
}>;
|
|
25
|
+
get(claimToken: string): Promise<ClaimReadResult>;
|
|
26
|
+
markReady(claimToken: string, data: {
|
|
27
|
+
apiKey: string;
|
|
28
|
+
organizationId: string;
|
|
29
|
+
}): Promise<boolean>;
|
|
30
|
+
markError(claimToken: string, error: string): Promise<boolean>;
|
|
31
|
+
/** One-shot: return the entry and delete it once ready/error. */
|
|
32
|
+
consume(claimToken: string): Promise<ClaimReadResult>;
|
|
33
|
+
}
|
|
34
|
+
/** Default single-process claim store. sha256(claim_token) is the map key;
|
|
35
|
+
* 10-minute TTL; pruned every minute + lazily on read. */
|
|
36
|
+
export declare function inMemoryClaimStore(ttlMs?: number): ClaimStore;
|
|
37
|
+
//# sourceMappingURL=claim-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-store.d.ts","sourceRoot":"","sources":["../../src/agent-auth/claim-store.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAExD,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAC1E;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD;;kDAEkD;AAClD,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1E,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAClD,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,iEAAiE;IACjE,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACvD;AAWD;2DAC2D;AAC3D,wBAAgB,kBAAkB,CAAC,KAAK,SAAiB,GAAG,UAAU,CAqDrE"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
/** Default single-process claim store. sha256(claim_token) is the map key;
|
|
3
|
+
* 10-minute TTL; pruned every minute + lazily on read. */
|
|
4
|
+
export function inMemoryClaimStore(ttlMs = 10 * 60 * 1000) {
|
|
5
|
+
const claims = new Map();
|
|
6
|
+
const hash = (t) => createHash("sha256").update(t).digest("hex");
|
|
7
|
+
const prune = () => {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
for (const [k, v] of claims)
|
|
10
|
+
if (now > v.expiresAt)
|
|
11
|
+
claims.delete(k);
|
|
12
|
+
};
|
|
13
|
+
const g = globalThis;
|
|
14
|
+
if (typeof globalThis !== "undefined" && !g.__billing_claim_cleanup) {
|
|
15
|
+
g.__billing_claim_cleanup = setInterval(prune, 60 * 1000);
|
|
16
|
+
g.__billing_claim_cleanup.unref?.();
|
|
17
|
+
}
|
|
18
|
+
const read = (claimToken) => {
|
|
19
|
+
prune();
|
|
20
|
+
const e = claims.get(hash(claimToken));
|
|
21
|
+
if (!e)
|
|
22
|
+
return { status: "not_found" };
|
|
23
|
+
if (e.status === "pending")
|
|
24
|
+
return { status: "pending", email: e.email };
|
|
25
|
+
if (e.status === "error")
|
|
26
|
+
return { status: "error", email: e.email, error: e.error ?? "unknown" };
|
|
27
|
+
return { status: "ready", email: e.email, apiKey: e.apiKey, organizationId: e.organizationId };
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
async create(email) {
|
|
31
|
+
prune();
|
|
32
|
+
const claimToken = `clm_${randomBytes(32).toString("base64url")}`;
|
|
33
|
+
claims.set(hash(claimToken), { email, status: "pending", expiresAt: Date.now() + ttlMs });
|
|
34
|
+
return { claimToken, expiresIn: Math.floor(ttlMs / 1000) };
|
|
35
|
+
},
|
|
36
|
+
async get(claimToken) {
|
|
37
|
+
return read(claimToken);
|
|
38
|
+
},
|
|
39
|
+
async markReady(claimToken, data) {
|
|
40
|
+
const e = claims.get(hash(claimToken));
|
|
41
|
+
if (!e)
|
|
42
|
+
return false;
|
|
43
|
+
e.status = "ready";
|
|
44
|
+
e.apiKey = data.apiKey;
|
|
45
|
+
e.organizationId = data.organizationId;
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
async markError(claimToken, error) {
|
|
49
|
+
const e = claims.get(hash(claimToken));
|
|
50
|
+
if (!e)
|
|
51
|
+
return false;
|
|
52
|
+
e.status = "error";
|
|
53
|
+
e.error = error;
|
|
54
|
+
return true;
|
|
55
|
+
},
|
|
56
|
+
async consume(claimToken) {
|
|
57
|
+
const result = read(claimToken);
|
|
58
|
+
if (result.status === "ready" || result.status === "error")
|
|
59
|
+
claims.delete(hash(claimToken));
|
|
60
|
+
return result;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=claim-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claim-store.js","sourceRoot":"","sources":["../../src/agent-auth/claim-store.ts"],"names":[],"mappings":"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"}
|