@usebillow/sdk 0.5.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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/dist/billing-C4RIMgH_.d.ts +1053 -0
- package/dist/billing-DZ4rIyg7.d.cts +1053 -0
- package/dist/billing-status-BZQN_gm7.d.cts +29 -0
- package/dist/billing-status-BZQN_gm7.d.ts +29 -0
- package/dist/chunk-CCG4F5FK.js +48 -0
- package/dist/chunk-CCG4F5FK.js.map +1 -0
- package/dist/chunk-Z6VXPONT.js +1493 -0
- package/dist/chunk-Z6VXPONT.js.map +1 -0
- package/dist/config.cjs +233 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +104 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.js +228 -0
- package/dist/config.js.map +1 -0
- package/dist/credits-C3Fe3TO0.d.cts +315 -0
- package/dist/credits-C3Fe3TO0.d.ts +315 -0
- package/dist/index.cjs +1560 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2379 -0
- package/dist/index.d.ts +2379 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/ingestion.cjs +259 -0
- package/dist/ingestion.cjs.map +1 -0
- package/dist/ingestion.d.cts +182 -0
- package/dist/ingestion.d.ts +182 -0
- package/dist/ingestion.js +252 -0
- package/dist/ingestion.js.map +1 -0
- package/dist/react.cjs +360 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +71 -0
- package/dist/react.d.ts +71 -0
- package/dist/react.js +153 -0
- package/dist/react.js.map +1 -0
- package/dist/server.cjs +98 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +54 -0
- package/dist/server.d.ts +54 -0
- package/dist/server.js +96 -0
- package/dist/server.js.map +1 -0
- package/dist/status.cjs +60 -0
- package/dist/status.cjs.map +1 -0
- package/dist/status.d.cts +31 -0
- package/dist/status.d.ts +31 -0
- package/dist/status.js +3 -0
- package/dist/status.js.map +1 -0
- package/dist/webhooks.cjs +157 -0
- package/dist/webhooks.cjs.map +1 -0
- package/dist/webhooks.d.cts +391 -0
- package/dist/webhooks.d.ts +391 -0
- package/dist/webhooks.js +143 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +169 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @usebillow/sdk
|
|
2
|
+
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c6c114a: Prepaid credits can be sold as Credit Packs. `credits.packs.list(currency)` lists the packs a customer can buy in one currency, cheapest first, with each pack's price, tax and total (Money), its credits and bonus (decimal strings of microcredits), `pricePerCredit` and `savingsBps`; `credits.packs.create`, `get` and `update` configure them. `credits.topUps.create(input, { idempotencyKey })` buys a pack and answers the top-up with its `checkoutUrl` and `replayed` flag, `credits.topUps.get` reads one, and `credits.topUps.list(customer, { status })` pages a customer's top-ups by cursor. Webhook payload types cover the new `credit_top_up.succeeded`, `credit_top_up.failed`, `credit_top_up.refunded` and `credit_top_up.unfulfilled` events, the customer data export types carry `topUps` and `reversals`, and `reversal_exceeds_consumption` joins the error codes.
|
|
8
|
+
- 6bf89c8: First public release on npm as `@usebillow/sdk`, under the MIT license. Ships dual ESM and CommonJS builds with complete type declarations for every entry point: `@usebillow/sdk`, `/config`, `/server`, `/react`, `/webhooks`, `/ingestion` and `/status`.
|
|
9
|
+
- 6bf89c8: A client with no `baseUrl` option and no `BILLOW_URL` environment variable now talks to the hosted API at `https://api.usebillow.com`, instead of `http://localhost:4001` with a warning. Local development and self-hosted servers set `baseUrl` (or `BILLOW_URL`).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DevCoat
|
|
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,274 @@
|
|
|
1
|
+
# @usebillow/sdk
|
|
2
|
+
|
|
3
|
+
The official client SDK for [Billow](https://usebillow.com) - a self-hostable billing
|
|
4
|
+
layer over Paymob & Stripe. Fully typed, ESM + CJS, with first-class React support.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install @usebillow/sdk
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Server client
|
|
11
|
+
|
|
12
|
+
The core client holds your **secret** key and is used server-side only.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { Billow } from "@usebillow/sdk";
|
|
16
|
+
|
|
17
|
+
const billow = new Billow(process.env.BILLOW_SECRET_KEY!);
|
|
18
|
+
|
|
19
|
+
const allowed = await billow.check({ customerId, featureId: "projects" });
|
|
20
|
+
await billow.track({ customerId, featureId: "api_calls", value: 1 });
|
|
21
|
+
const { entitlements } = await billow.entitlements(customerId);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The client talks to the hosted API, `https://api.usebillow.com`. For local development or a
|
|
25
|
+
self-hosted server, set the `baseUrl` option (or the `BILLOW_URL` environment variable, which the
|
|
26
|
+
option overrides):
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const billow = new Billow(process.env.BILLOW_SECRET_KEY!, {
|
|
30
|
+
baseUrl: "http://localhost:4001", // your local Billow server
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For a server-controlled off-session payment, charge the customer's active default saved card
|
|
35
|
+
with a required idempotency key:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
await billow.charges.createMerchantInitiated(
|
|
39
|
+
{
|
|
40
|
+
customerId,
|
|
41
|
+
paymentMethodId,
|
|
42
|
+
amount: 5_000,
|
|
43
|
+
currency: "EGP",
|
|
44
|
+
items: [{ name: "Automatic top-up", amount: 5_000, quantity: 1 }],
|
|
45
|
+
},
|
|
46
|
+
{ idempotencyKey: `topup:${attemptId}` },
|
|
47
|
+
);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Refunds take a required idempotency key too: retrying under the same key (after a timeout, say)
|
|
51
|
+
returns the same refund instead of refunding twice, with `replayed: true`. Pass `{}` instead of
|
|
52
|
+
`{ amount }` to refund the whole remaining balance:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
const refund = await billow.charges.refund(
|
|
56
|
+
chargeId,
|
|
57
|
+
{ amount: 2_000 },
|
|
58
|
+
{ idempotencyKey: `refund:${requestId}` },
|
|
59
|
+
);
|
|
60
|
+
if (refund.status === "failed") {
|
|
61
|
+
// The provider declined it: nothing was refunded. Ask again under a new key.
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Check `status` even when the call resolves. A refund the provider declines answers
|
|
66
|
+
`502 provider_error`, which throws by default; with `maxRetries` above 0 the SDK retries that 502
|
|
67
|
+
under the same key and resolves with the declined refund (`status: "failed"`, `replayed: true`).
|
|
68
|
+
|
|
69
|
+
Prepaid credit grants take a required idempotency key as well; a retry under it returns the same
|
|
70
|
+
grant with `replayed: true`. Credit quantities are decimal strings of microcredits
|
|
71
|
+
(1 credit = 1,000,000), never numbers - use `BigInt` for arithmetic:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const grant = await billow.credits.grants.create(
|
|
75
|
+
{ customerId, kind: "promotional", amount: "5000000" },
|
|
76
|
+
{ idempotencyKey: `welcome:${customerId}` },
|
|
77
|
+
);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Read a customer's credits back - the balance (true at its `asOf`), the ledger newest first (it
|
|
81
|
+
pages by cursor, so entries arriving mid-walk are never skipped or repeated), and daily usage:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const balance = await billow.credits.balance.get(customerId);
|
|
85
|
+
for await (const entry of billow.credits.ledger.list(customerId)) {
|
|
86
|
+
// entry.type, entry.availableDelta, entry.availableAfter, ...
|
|
87
|
+
}
|
|
88
|
+
const usage = await billow.credits.usage.get(customerId, { groupBy: "category" });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Auto-metering (LLM tokens & any usage source)
|
|
92
|
+
|
|
93
|
+
Stop hand-instrumenting usage. `@usebillow/sdk/ingestion` wraps your calls and reports
|
|
94
|
+
usage to `track()` for you - **server-side only** (a browser can't be trusted to
|
|
95
|
+
self-report) and **non-blocking** (metering can never delay or break the wrapped
|
|
96
|
+
call; a failed `track()` retries under an idempotency key, then drops silently).
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { Billow } from "@usebillow/sdk";
|
|
100
|
+
import { createUsageMeter, meterOpenAI } from "@usebillow/sdk/ingestion";
|
|
101
|
+
import OpenAI from "openai";
|
|
102
|
+
|
|
103
|
+
const billow = new Billow(process.env.BILLOW_SECRET_KEY!);
|
|
104
|
+
const meter = createUsageMeter(billow);
|
|
105
|
+
|
|
106
|
+
// Every completion now auto-meters its token usage (read from the provider response).
|
|
107
|
+
const openai = meterOpenAI(new OpenAI(), {
|
|
108
|
+
meter,
|
|
109
|
+
feature: "tokens",
|
|
110
|
+
customer: (params) => params.user as string, // your billow customer id
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await openai.chat.completions.create({ model: "gpt-4o", messages, user: "cus_123" });
|
|
114
|
+
// For a STREAMED call, add stream_options:{ include_usage:true } so tokens can be counted.
|
|
115
|
+
// Streamed calls are metered when consumed the normal way (await, then `for await`).
|
|
116
|
+
// Consuming via `.tee()` / `.toReadableStream()` / `.withResponse()` still works but
|
|
117
|
+
// bypasses the token tap.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Vercel AI SDK - meter the result of `generateText`/`streamText` (never touches the
|
|
121
|
+
stream). Works with v4 and v5 usage shapes:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { meterAIResult } from "@usebillow/sdk/ingestion";
|
|
125
|
+
|
|
126
|
+
const result = streamText({ model, prompt });
|
|
127
|
+
meterAIResult(result, { meter, feature: "tokens", customer: "cus_123" });
|
|
128
|
+
return result.toDataStreamResponse();
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Any other source - wrap any async function with the generic primitive:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { meterFunction } from "@usebillow/sdk/ingestion";
|
|
135
|
+
|
|
136
|
+
const upload = meterFunction(putObject, meter, (res, [file]) => ({
|
|
137
|
+
customerId: file.ownerId,
|
|
138
|
+
featureId: "storage_bytes",
|
|
139
|
+
value: res.size,
|
|
140
|
+
}));
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
In serverless handlers with a **non-streamed** response (`generateText`, a blocking
|
|
144
|
+
completion), `await meter.flush()` before returning so buffered records aren't lost
|
|
145
|
+
when the process freezes. Do **not** await `flush()` before returning a **streamed**
|
|
146
|
+
response - a stream's usage resolves only after the client consumes it, so blocking
|
|
147
|
+
on flush first would hang the response. There, flush after the stream finishes (the
|
|
148
|
+
AI SDK's `onFinish`, or your platform's `waitUntil`); `meterAIResult` already tracks
|
|
149
|
+
the pending usage so it's recorded in the background.
|
|
150
|
+
|
|
151
|
+
## React (Next.js, Remix, …)
|
|
152
|
+
|
|
153
|
+
Mount the server handler so the browser never sees your secret key, then use the hooks.
|
|
154
|
+
The customer is resolved from **your** session - never chosen by the browser.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// app/api/billow/[...billow]/route.ts
|
|
158
|
+
import { createBillowHandler } from "@usebillow/sdk/server";
|
|
159
|
+
import { billow } from "@/lib/billow";
|
|
160
|
+
|
|
161
|
+
const handler = createBillowHandler(billow, {
|
|
162
|
+
identify: async (req) => getUserIdFromSession(req),
|
|
163
|
+
});
|
|
164
|
+
export { handler as GET, handler as POST };
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
// app/providers.tsx
|
|
169
|
+
"use client";
|
|
170
|
+
import { BillowProvider, useCheck, useEntitlements } from "@usebillow/sdk/react";
|
|
171
|
+
|
|
172
|
+
export function Providers({ children }) {
|
|
173
|
+
return <BillowProvider basePath="/api/billow">{children}</BillowProvider>;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Pricing table (publishable key, no backend)
|
|
178
|
+
|
|
179
|
+
Read the public catalog straight from the browser with a **publishable** key
|
|
180
|
+
(`bl_<env>_pk_…`) - CORS is enabled for the catalog, so a pure SPA needs no backend:
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import { usePublishableProducts } from "@usebillow/sdk/react";
|
|
184
|
+
|
|
185
|
+
function Pricing() {
|
|
186
|
+
const { products, isLoading } = usePublishableProducts({
|
|
187
|
+
publishableKey: "bl_live_pk_…",
|
|
188
|
+
baseUrl: "https://api.usebillow.com",
|
|
189
|
+
});
|
|
190
|
+
if (isLoading) return null;
|
|
191
|
+
return products.map((p) => /* render p.name + p.prices */ null);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Publishable keys read only public product/price data. Customer entitlements and
|
|
196
|
+
checkout still go through the server handler (above) or a portal session.
|
|
197
|
+
|
|
198
|
+
## Verify webhooks
|
|
199
|
+
|
|
200
|
+
Verification is async (Web Crypto) so it runs in Node, edge, and Workers alike.
|
|
201
|
+
Always pass the **raw** request body.
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { constructEvent } from "@usebillow/sdk/webhooks";
|
|
205
|
+
|
|
206
|
+
const event = await constructEvent(rawBody, req.headers["billow-signature"], endpointSecret);
|
|
207
|
+
switch (event.type) {
|
|
208
|
+
case "invoice.paid": // event.data is typed
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`constructEvent` throws `WebhookVerificationError` on a bad signature or a timestamp
|
|
214
|
+
outside the freshness window (default 5 min). Use `verifySignature(...)` for a
|
|
215
|
+
boolean check.
|
|
216
|
+
|
|
217
|
+
## Config as code
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { defineConfig, syncConfig } from "@usebillow/sdk/config";
|
|
221
|
+
|
|
222
|
+
const config = defineConfig({ features: [/* … */], products: [/* … */] });
|
|
223
|
+
await syncConfig(billow, config); // additive - never deletes
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
## Pay an outstanding invoice
|
|
228
|
+
|
|
229
|
+
Call from your backend with a secret key after checking that the signed-in customer owns the invoice:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
const payment = await billow.invoices.pay(invoiceId, {
|
|
233
|
+
returnUrl: "https://your-app.example/billing",
|
|
234
|
+
});
|
|
235
|
+
// Redirect the customer to payment.checkoutUrl.
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
REST: `POST /v1/invoices/:id/pay` with `{ "returnUrl": "https://your-app.example/billing" }`.
|
|
239
|
+
The response contains `invoiceId`, `chargeId`, `checkoutUrl`, `status: "processing"`, and `expiresAt`.
|
|
240
|
+
The amount comes from the existing open invoice, including tax. An active checkout is reused;
|
|
241
|
+
conflicting or unresolved payments return HTTP 409. Never put a secret key in a browser link.
|
|
242
|
+
|
|
243
|
+
For a customer-scoped portal session, use `portal.invoices.pay(invoiceId)` or
|
|
244
|
+
`POST /portal/invoices/:id/pay`. Billow enforces invoice ownership for that session.
|
|
245
|
+
The hosted portal also offers Pay now on its overview and invoice list.
|
|
246
|
+
|
|
247
|
+
A redirect back to your application does not prove payment. Refresh the invoice/subscription or
|
|
248
|
+
handle the signed `invoice.paid` and subscription events. Confirmed payment settles the same invoice
|
|
249
|
+
and restores a past-due subscription through Billow's normal settlement process.
|
|
250
|
+
|
|
251
|
+
### Saved card management
|
|
252
|
+
|
|
253
|
+
List, choose a default, or remove a customer's saved cards from your server:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
const cards = await billow.customers.paymentMethods.list(customerId);
|
|
257
|
+
await billow.customers.paymentMethods.setDefault(customerId, cardId);
|
|
258
|
+
await billow.customers.paymentMethods.remove(customerId, cardId);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
A `BillowPortal` client offers the same operations as `portal.paymentMethods.list()`,
|
|
262
|
+
`portal.paymentMethods.setDefault(cardId)`, and `portal.paymentMethods.remove(cardId)`.
|
|
263
|
+
Its session fixes the customer identity. Responses include masked card details,
|
|
264
|
+
`expiryStatus`, `canSetDefault`, `canRemove`, and `removalBlockedReason`; never card tokens.
|
|
265
|
+
|
|
266
|
+
Choosing a default updates renewal cards on active, trialing, and past-due subscriptions.
|
|
267
|
+
A default or subscription renewal card cannot be removed until a replacement is selected.
|
|
268
|
+
Once those subscriptions have ended, every card can be removed, including the default.
|
|
269
|
+
Cards remain valid through their expiry month and are flagged during their last three
|
|
270
|
+
calendar months. Missing expiry data is reported as `unknown`.
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT. See the `LICENSE` file shipped with this package.
|