@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +274 -0
  4. package/dist/billing-C4RIMgH_.d.ts +1053 -0
  5. package/dist/billing-DZ4rIyg7.d.cts +1053 -0
  6. package/dist/billing-status-BZQN_gm7.d.cts +29 -0
  7. package/dist/billing-status-BZQN_gm7.d.ts +29 -0
  8. package/dist/chunk-CCG4F5FK.js +48 -0
  9. package/dist/chunk-CCG4F5FK.js.map +1 -0
  10. package/dist/chunk-Z6VXPONT.js +1493 -0
  11. package/dist/chunk-Z6VXPONT.js.map +1 -0
  12. package/dist/config.cjs +233 -0
  13. package/dist/config.cjs.map +1 -0
  14. package/dist/config.d.cts +104 -0
  15. package/dist/config.d.ts +104 -0
  16. package/dist/config.js +228 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/credits-C3Fe3TO0.d.cts +315 -0
  19. package/dist/credits-C3Fe3TO0.d.ts +315 -0
  20. package/dist/index.cjs +1560 -0
  21. package/dist/index.cjs.map +1 -0
  22. package/dist/index.d.cts +2379 -0
  23. package/dist/index.d.ts +2379 -0
  24. package/dist/index.js +4 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/ingestion.cjs +259 -0
  27. package/dist/ingestion.cjs.map +1 -0
  28. package/dist/ingestion.d.cts +182 -0
  29. package/dist/ingestion.d.ts +182 -0
  30. package/dist/ingestion.js +252 -0
  31. package/dist/ingestion.js.map +1 -0
  32. package/dist/react.cjs +360 -0
  33. package/dist/react.cjs.map +1 -0
  34. package/dist/react.d.cts +71 -0
  35. package/dist/react.d.ts +71 -0
  36. package/dist/react.js +153 -0
  37. package/dist/react.js.map +1 -0
  38. package/dist/server.cjs +98 -0
  39. package/dist/server.cjs.map +1 -0
  40. package/dist/server.d.cts +54 -0
  41. package/dist/server.d.ts +54 -0
  42. package/dist/server.js +96 -0
  43. package/dist/server.js.map +1 -0
  44. package/dist/status.cjs +60 -0
  45. package/dist/status.cjs.map +1 -0
  46. package/dist/status.d.cts +31 -0
  47. package/dist/status.d.ts +31 -0
  48. package/dist/status.js +3 -0
  49. package/dist/status.js.map +1 -0
  50. package/dist/webhooks.cjs +157 -0
  51. package/dist/webhooks.cjs.map +1 -0
  52. package/dist/webhooks.d.cts +391 -0
  53. package/dist/webhooks.d.ts +391 -0
  54. package/dist/webhooks.js +143 -0
  55. package/dist/webhooks.js.map +1 -0
  56. package/package.json +169 -0
@@ -0,0 +1,1493 @@
1
+ // src/client.ts
2
+ var REQUEST_ID_HEADER = "x-request-id";
3
+ function toQuery(params) {
4
+ const q = new URLSearchParams();
5
+ for (const [k, v] of Object.entries(params)) {
6
+ if (v !== void 0 && v !== null && v !== "") q.set(k, String(v));
7
+ }
8
+ const s = q.toString();
9
+ return s ? `?${s}` : "";
10
+ }
11
+ var BillowApiError = class extends Error {
12
+ status;
13
+ /**
14
+ * A known {@link BillowErrorCode} (widened to `string` so a newer server's code still type-checks),
15
+ * or one the SDK raises itself: `invalid_response` for a success (2xx other than 204) whose body
16
+ * was empty or could not be read or parsed. The request reached billow and was answered, but what
17
+ * it answered is unknown - for a write, treat the outcome as UNKNOWN (retry it under the same
18
+ * idempotency key), never as done or as refused.
19
+ */
20
+ code;
21
+ /** Structured error context from the server (e.g. per-field validation issues), when present. */
22
+ details;
23
+ /** billow's per-request id (`x-request-id`) — quote it in a bug report to trace the server log. */
24
+ requestId;
25
+ constructor(status, code, message, details, requestId) {
26
+ super(message);
27
+ this.name = "BillowApiError";
28
+ this.status = status;
29
+ this.code = code;
30
+ this.details = details;
31
+ this.requestId = requestId;
32
+ }
33
+ };
34
+ var DEFAULT_TIMEOUT_MS = 6e4;
35
+ var DEFAULT_RETRY_BACKOFF_MS = 500;
36
+ function makeContext(bearer, opts) {
37
+ return {
38
+ fetch: opts.fetch ?? fetch,
39
+ baseUrl: resolveBaseUrl(opts.baseUrl),
40
+ bearer,
41
+ timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,
42
+ maxRetries: opts.maxRetries ?? 0,
43
+ retryBackoffMs: opts.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS
44
+ };
45
+ }
46
+ function isRetryable(status, safeToRepeat2) {
47
+ if (status === 429) return true;
48
+ if (status === null) return safeToRepeat2;
49
+ return safeToRepeat2 && status >= 500 && status <= 599;
50
+ }
51
+ function backoffMs(base, attempt, retryAfter) {
52
+ if (retryAfter) {
53
+ const secs = Number(retryAfter);
54
+ if (Number.isFinite(secs) && secs >= 0) return secs * 1e3;
55
+ }
56
+ return base * 2 ** attempt;
57
+ }
58
+ function abortReason(signal) {
59
+ return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
60
+ }
61
+ function sleep(ms, signal) {
62
+ return new Promise((resolve, reject) => {
63
+ if (signal?.aborted) return reject(abortReason(signal));
64
+ const timer = setTimeout(resolve, ms);
65
+ signal?.addEventListener(
66
+ "abort",
67
+ () => {
68
+ clearTimeout(timer);
69
+ reject(abortReason(signal));
70
+ },
71
+ { once: true }
72
+ );
73
+ });
74
+ }
75
+ async function sendWithResilience(ctx, url, init, opts) {
76
+ const { timeoutMs, safeToRepeat: safeToRepeat2, callerSignal } = opts;
77
+ if (callerSignal?.aborted) throw abortReason(callerSignal);
78
+ for (let attempt = 0; ; attempt++) {
79
+ const controller = new AbortController();
80
+ let timedOut = false;
81
+ const onCallerAbort = () => controller.abort(abortReason(callerSignal));
82
+ callerSignal?.addEventListener("abort", onCallerAbort, { once: true });
83
+ const timer = timeoutMs > 0 ? setTimeout(() => {
84
+ timedOut = true;
85
+ controller.abort();
86
+ }, timeoutMs) : void 0;
87
+ let res = null;
88
+ let err;
89
+ try {
90
+ res = await ctx.fetch(url, { ...init, signal: controller.signal });
91
+ } catch (e) {
92
+ err = e;
93
+ } finally {
94
+ if (timer) clearTimeout(timer);
95
+ callerSignal?.removeEventListener("abort", onCallerAbort);
96
+ }
97
+ if (callerSignal?.aborted) throw abortReason(callerSignal);
98
+ if (res?.ok) return res;
99
+ const status = res ? res.status : null;
100
+ if (attempt < ctx.maxRetries && isRetryable(status, safeToRepeat2)) {
101
+ await sleep(
102
+ backoffMs(ctx.retryBackoffMs, attempt, res?.headers.get("retry-after") ?? null),
103
+ callerSignal
104
+ );
105
+ continue;
106
+ }
107
+ if (res) return res;
108
+ if (timedOut) throw new Error(`billow request timed out after ${timeoutMs}ms`);
109
+ throw err instanceof Error ? err : new Error("billow request failed");
110
+ }
111
+ }
112
+ function safeToRepeat(method, hasIdempotencyKey) {
113
+ return method === "GET" || method === "HEAD" || hasIdempotencyKey;
114
+ }
115
+ var DEFAULT_BASE_URL = "https://api.usebillow.com";
116
+ function resolveBaseUrl(explicit) {
117
+ const fromEnv = typeof process !== "undefined" ? process.env?.["BILLOW_URL"] : void 0;
118
+ return (explicit || fromEnv || DEFAULT_BASE_URL).replace(/\/$/, "");
119
+ }
120
+ async function apiRequest(ctx, method, path, body, options) {
121
+ return (await apiRequestWithStatus(ctx, method, path, body, options)).data;
122
+ }
123
+ async function apiRequestWithStatus(ctx, method, path, body, options) {
124
+ const init = {
125
+ method,
126
+ headers: {
127
+ authorization: `Bearer ${ctx.bearer}`,
128
+ ...body ? { "content-type": "application/json" } : {},
129
+ ...options?.idempotencyKey ? { "idempotency-key": options.idempotencyKey } : {}
130
+ }
131
+ };
132
+ if (body) init.body = JSON.stringify(body);
133
+ const res = await sendWithResilience(ctx, `${ctx.baseUrl}${path}`, init, {
134
+ timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,
135
+ safeToRepeat: safeToRepeat(method, !!options?.idempotencyKey),
136
+ callerSignal: options?.signal
137
+ });
138
+ const text = await res.text().catch(() => null);
139
+ if (!res.ok) throw errorFrom(res, safeParse(text ?? ""));
140
+ return { status: res.status, data: parseSuccessBody(res, text) };
141
+ }
142
+ function requestIdOf(res) {
143
+ return res.headers.get(REQUEST_ID_HEADER) ?? void 0;
144
+ }
145
+ function safeParse(text) {
146
+ if (text.trim() === "") return {};
147
+ try {
148
+ return JSON.parse(text);
149
+ } catch {
150
+ return {};
151
+ }
152
+ }
153
+ function parseSuccessBody(res, text) {
154
+ if (res.status === 204) return {};
155
+ if (text !== null && text.trim() !== "") {
156
+ try {
157
+ return JSON.parse(text);
158
+ } catch {
159
+ }
160
+ }
161
+ throw new BillowApiError(
162
+ res.status,
163
+ "invalid_response",
164
+ `billow returned an empty or unreadable response body (HTTP ${res.status})`,
165
+ void 0,
166
+ requestIdOf(res)
167
+ );
168
+ }
169
+ function errorFrom(res, data) {
170
+ const err = data.error ?? {};
171
+ return new BillowApiError(
172
+ res.status,
173
+ err.code ?? "error",
174
+ err.message ?? String(res.status),
175
+ err.details,
176
+ requestIdOf(res)
177
+ );
178
+ }
179
+ async function apiRequestBinary(ctx, method, path, options) {
180
+ const res = await sendWithResilience(
181
+ ctx,
182
+ `${ctx.baseUrl}${path}`,
183
+ { method, headers: { authorization: `Bearer ${ctx.bearer}` } },
184
+ {
185
+ timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,
186
+ safeToRepeat: safeToRepeat(method, false),
187
+ callerSignal: options?.signal
188
+ }
189
+ );
190
+ if (!res.ok) throw errorFrom(res, safeParse(await res.text().catch(() => "")));
191
+ return res.arrayBuffer();
192
+ }
193
+
194
+ // src/error-codes.ts
195
+ var BILLOW_ERROR_CODES = [
196
+ "not_found",
197
+ "validation_error",
198
+ "authentication_error",
199
+ "api_key_expired",
200
+ "permission_denied",
201
+ "conflict",
202
+ "invalid_state_transition",
203
+ "provider_error",
204
+ "rate_limited",
205
+ "quota_exceeded",
206
+ "internal_error",
207
+ // Prepaid credits. 402 is shared with `quota_exceeded`: switch on the code, never the status.
208
+ "insufficient_credits",
209
+ "account_frozen",
210
+ "account_closed",
211
+ "idempotency_conflict",
212
+ "commit_exceeds_hold",
213
+ "reversal_exceeds_consumption"
214
+ ];
215
+
216
+ // src/money.ts
217
+ var CURRENCY_EXPONENTS = {
218
+ // Zero-decimal
219
+ BIF: 0,
220
+ CLP: 0,
221
+ DJF: 0,
222
+ GNF: 0,
223
+ ISK: 0,
224
+ JPY: 0,
225
+ KMF: 0,
226
+ KRW: 0,
227
+ PYG: 0,
228
+ RWF: 0,
229
+ UGX: 0,
230
+ VND: 0,
231
+ VUV: 0,
232
+ XAF: 0,
233
+ XOF: 0,
234
+ XPF: 0,
235
+ // Three-decimal
236
+ BHD: 3,
237
+ IQD: 3,
238
+ JOD: 3,
239
+ KWD: 3,
240
+ LYD: 3,
241
+ OMR: 3,
242
+ TND: 3,
243
+ // Four-decimal
244
+ CLF: 4,
245
+ UYW: 4
246
+ };
247
+ function currencyExponent(currency) {
248
+ return CURRENCY_EXPONENTS[currency.toUpperCase()] ?? 2;
249
+ }
250
+ function toMinorUnits(major, currency) {
251
+ return Math.round(major * 10 ** currencyExponent(currency));
252
+ }
253
+ function toMajorUnits(minor, currency) {
254
+ return minor / 10 ** currencyExponent(currency);
255
+ }
256
+ function formatMoney(minor, currency) {
257
+ const exponent = currencyExponent(currency);
258
+ const major = minor / 10 ** exponent;
259
+ try {
260
+ return new Intl.NumberFormat("en", {
261
+ style: "currency",
262
+ currency,
263
+ minimumFractionDigits: exponent,
264
+ maximumFractionDigits: exponent
265
+ }).format(major);
266
+ } catch {
267
+ return `${major.toFixed(exponent)} ${currency}`;
268
+ }
269
+ }
270
+
271
+ // src/types/admin-integrations.ts
272
+ function isSecretField(field) {
273
+ return field.secret === true || field.type === "password";
274
+ }
275
+
276
+ // src/pagination.ts
277
+ function makePagedList(fetchFirst, startWalk) {
278
+ let firstPage;
279
+ const first = () => {
280
+ if (!firstPage) {
281
+ const p = fetchFirst();
282
+ firstPage = p;
283
+ p.catch(() => {
284
+ if (firstPage === p) firstPage = void 0;
285
+ });
286
+ }
287
+ return firstPage;
288
+ };
289
+ async function* iterate() {
290
+ const next = startWalk();
291
+ let page = await first();
292
+ while (page) {
293
+ yield* page.data;
294
+ const following = next(page);
295
+ page = following ? await following : null;
296
+ }
297
+ }
298
+ const listAll = async () => {
299
+ const out = [];
300
+ for await (const item of iterate()) out.push(item);
301
+ return out;
302
+ };
303
+ return Object.assign(first(), {
304
+ [Symbol.asyncIterator]: iterate,
305
+ listAll
306
+ });
307
+ }
308
+ function makeListPromise(fetchPage, params) {
309
+ return makePagedList(
310
+ () => fetchPage(params),
311
+ () => {
312
+ let limit;
313
+ let offset = params.offset ?? 0;
314
+ return (page) => {
315
+ limit ??= page.limit > 0 ? page.limit : params.limit ?? 0;
316
+ offset += page.data.length;
317
+ if (!(limit > 0 && page.data.length >= limit)) return null;
318
+ return fetchPage({ ...params, limit, offset });
319
+ };
320
+ }
321
+ );
322
+ }
323
+ function makeCursorListPromise(fetchPage, params) {
324
+ return makePagedList(
325
+ () => fetchPage(params),
326
+ () => (page) => page.nextCursor === null ? null : fetchPage({ ...params, limit: page.limit, cursor: page.nextCursor })
327
+ );
328
+ }
329
+
330
+ // src/resources/charges.ts
331
+ function createChargesResource(ctx) {
332
+ return {
333
+ /** Create a customer-present charge -> returns a hosted checkout URL. Pass an
334
+ * `idempotencyKey` to make the create safe to retry (with `maxRetries`). */
335
+ create: (input, options) => apiRequest(ctx, "POST", "/v1/charges", input, options),
336
+ /** Charge the customer's current saved card without customer presence. */
337
+ createMerchantInitiated: (input, options) => apiRequest(ctx, "POST", "/v1/charges/mit", input, options),
338
+ /** Fetch a charge. Pass `{ sync: true }` to force an authoritative provider pull. */
339
+ get: (id, opts) => apiRequest(
340
+ ctx,
341
+ "GET",
342
+ `/v1/charges/${encodeURIComponent(id)}${opts?.sync ? "?sync=true" : ""}`,
343
+ void 0,
344
+ opts
345
+ ),
346
+ /** List charges (paginated). Filters: status, currency, customer (external id), from, to. */
347
+ list: (params = {}, options) => makeListPromise(
348
+ (p) => apiRequest(
349
+ ctx,
350
+ "GET",
351
+ `/v1/charges${toQuery(p)}`,
352
+ void 0,
353
+ options
354
+ ),
355
+ params
356
+ ),
357
+ /** Download the one-off charge's receipt as a PDF (raw bytes). */
358
+ receipt: (id, options) => apiRequestBinary(ctx, "GET", `/v1/charges/${encodeURIComponent(id)}/receipt.pdf`, options),
359
+ /** Download the immutable credit note for a succeeded refund. */
360
+ creditNote: (chargeId, refundId, options) => apiRequestBinary(
361
+ ctx,
362
+ "GET",
363
+ `/v1/charges/${encodeURIComponent(chargeId)}/refunds/${encodeURIComponent(refundId)}/credit-note.pdf`,
364
+ options
365
+ ),
366
+ /**
367
+ * Refund a succeeded charge: `{ amount }` (minor units) for a partial, `{}` for its whole
368
+ * remaining balance. The `idempotencyKey` is required and must identify this one refund
369
+ * (e.g. your own refund request's id): a retry under it - yours, or the SDK's own
370
+ * `maxRetries` - answers the same refund, in whatever status, instead of refunding again.
371
+ * Reuse the key whenever the outcome is unknown (a timeout or dropped connection); a new
372
+ * key asks for a new refund, e.g. after this one `failed`. `replayed` tells the refund an
373
+ * earlier request under the key made (`true`) from one this call made (`false`).
374
+ *
375
+ * Always check `status`: resolving does not mean refunded. A provider that declines the
376
+ * refund answers `502 provider_error`, which throws with `maxRetries: 0` (the default); with
377
+ * `maxRetries > 0` the SDK retries that 502 under the same key, so the call resolves instead
378
+ * with the declined refund replayed: `status: "failed"`, `replayed: true`.
379
+ */
380
+ refund: async (chargeId, input, options) => {
381
+ const { status, data } = await apiRequestWithStatus(
382
+ ctx,
383
+ "POST",
384
+ `/v1/charges/${encodeURIComponent(chargeId)}/refund`,
385
+ input,
386
+ options
387
+ );
388
+ return { ...data, replayed: status === 200 };
389
+ },
390
+ /** A charge's refunds, newest first (`needsReview` flags one awaiting an Operator). */
391
+ listRefunds: (chargeId, options) => apiRequest(
392
+ ctx,
393
+ "GET",
394
+ `/v1/charges/${encodeURIComponent(chargeId)}/refunds`,
395
+ void 0,
396
+ options
397
+ ),
398
+ /** Reconcile a pending refund with the provider now; resolves to the refund as it stands. */
399
+ recheckRefund: (chargeId, refundId, options) => apiRequest(
400
+ ctx,
401
+ "POST",
402
+ `${refundPath(chargeId, refundId)}/recheck`,
403
+ void 0,
404
+ options
405
+ ),
406
+ /**
407
+ * Record the outcome you verified in the provider dashboard for a refund awaiting review
408
+ * (`needsReview`): `succeeded` settles it with a credit note, `failed` releases its amount.
409
+ * A refund with a provider conflict (`providerConflictStatus`) resolves only as `succeeded`.
410
+ */
411
+ resolveRefund: (chargeId, refundId, input, options) => apiRequest(ctx, "POST", `${refundPath(chargeId, refundId)}/resolve`, input, options)
412
+ };
413
+ }
414
+ function refundPath(chargeId, refundId) {
415
+ return `/v1/charges/${encodeURIComponent(chargeId)}/refunds/${encodeURIComponent(refundId)}`;
416
+ }
417
+ function createCheckoutMethodsResource(ctx) {
418
+ return {
419
+ /** The customer-present methods + fees offered for a currency (for a method picker). */
420
+ list: (params, options) => apiRequest(
421
+ ctx,
422
+ "GET",
423
+ `/v1/checkout-methods${toQuery(params)}`,
424
+ void 0,
425
+ options
426
+ )
427
+ };
428
+ }
429
+
430
+ // src/resources/credits.ts
431
+ function createCreditsResource(ctx) {
432
+ return {
433
+ grants: {
434
+ /**
435
+ * Grant prepaid credits to a Customer. The `idempotencyKey` is required and must identify
436
+ * this one grant (e.g. your own promotion or support-ticket id): a retry under it - yours,
437
+ * or the SDK's own `maxRetries` - answers the same grant instead of granting twice, with
438
+ * `replayed: true`. Reusing the key for a different grant throws `idempotency_conflict`
439
+ * (409). Credit quantities are decimal strings of microcredits in both directions.
440
+ */
441
+ create: async (input, options) => {
442
+ const { status, data } = await apiRequestWithStatus(
443
+ ctx,
444
+ "POST",
445
+ "/v1/credits/grants",
446
+ input,
447
+ options
448
+ );
449
+ return { ...data, replayed: status === 200 };
450
+ }
451
+ },
452
+ packs: {
453
+ /**
454
+ * The Credit Packs a customer can buy in `currency` (ISO 4217), cheapest first: credits and
455
+ * bonus (decimal strings of microcredits), price (minor units), and the derived
456
+ * `pricePerCredit` and `savingsBps` - so your pricing page never hardcodes a price.
457
+ */
458
+ list: (currency, options) => apiRequest(
459
+ ctx,
460
+ "GET",
461
+ `/v1/credits/packs${toQuery({ currency })}`,
462
+ void 0,
463
+ options
464
+ ),
465
+ /** Configure a Credit Pack on a one-time catalog Price. */
466
+ create: (input, options) => apiRequest(ctx, "POST", "/v1/credits/packs", input, options),
467
+ /** A Credit Pack's configuration, archived or not. */
468
+ get: (packId, options) => apiRequest(
469
+ ctx,
470
+ "GET",
471
+ `/v1/credits/packs/${encodeURIComponent(packId)}`,
472
+ void 0,
473
+ options
474
+ ),
475
+ /** Edit a pack's terms or Price, or archive it; top-ups already made keep their terms. */
476
+ update: (packId, input, options) => apiRequest(
477
+ ctx,
478
+ "PATCH",
479
+ `/v1/credits/packs/${encodeURIComponent(packId)}`,
480
+ input,
481
+ options
482
+ )
483
+ },
484
+ topUps: {
485
+ /**
486
+ * Buy a Credit Pack for a customer: answers the top-up with `checkoutUrl`, the hosted
487
+ * checkout to send the buyer to. Credits are granted when the payment succeeds (listen for
488
+ * `credit_top_up.succeeded`). The `idempotencyKey` is required and must identify this one
489
+ * purchase: a retry under it answers the same top-up as it now stands (`replayed: true`) and
490
+ * buys nothing again; reusing it for another purchase throws `idempotency_conflict` (409).
491
+ * A retry can also throw `conflict` (409), with `details.reason`: `checkout_in_progress` -
492
+ * the first request is still opening the checkout, so retry the same key shortly - or
493
+ * `checkout_unavailable` - its checkout could not be opened and never will be, so buy again
494
+ * under a new key.
495
+ */
496
+ create: async (input, options) => {
497
+ const { status, data } = await apiRequestWithStatus(
498
+ ctx,
499
+ "POST",
500
+ "/v1/credits/top-ups",
501
+ input,
502
+ options
503
+ );
504
+ return { ...data, replayed: status === 200 };
505
+ },
506
+ /** A top-up as it now stands: status, checkout, grants, and what refunds revoked. */
507
+ get: (topUpId, options) => apiRequest(
508
+ ctx,
509
+ "GET",
510
+ `/v1/credits/top-ups/${encodeURIComponent(topUpId)}`,
511
+ void 0,
512
+ options
513
+ ),
514
+ /**
515
+ * A customer's top-ups (by external id), newest first, optionally of one status.
516
+ * Auto-paginating by cursor: `await` the first page, `for await (…)` every top-up, or
517
+ * `.listAll()` to collect them.
518
+ */
519
+ list: (customer, params = {}, options) => {
520
+ const { status, ...page } = params;
521
+ return makeCursorListPromise(
522
+ (p) => apiRequest(
523
+ ctx,
524
+ "GET",
525
+ `/v1/credits/top-ups${toQuery({ customer, status, ...p })}`,
526
+ void 0,
527
+ options
528
+ ),
529
+ page
530
+ );
531
+ }
532
+ },
533
+ balance: {
534
+ /**
535
+ * A Customer's credit balance (by external id): spendable, held, consumed this period,
536
+ * expiring soon, by kind and category, the current period and the threshold level, true at
537
+ * `asOf`. A Customer with no credit account yet reads as zeros.
538
+ */
539
+ get: (customer, options) => apiRequest(
540
+ ctx,
541
+ "GET",
542
+ `/v1/credits/balance${toQuery({ customer })}`,
543
+ void 0,
544
+ options
545
+ )
546
+ },
547
+ ledger: {
548
+ /**
549
+ * A Customer's credit ledger (by external id), newest first. Auto-paginating by cursor:
550
+ * `await` the first page, `for await (…)` every entry, or `.listAll()` to collect them -
551
+ * entries arriving meanwhile never make the walk skip or repeat one.
552
+ */
553
+ list: (customer, params = {}, options) => makeCursorListPromise(
554
+ (p) => apiRequest(
555
+ ctx,
556
+ "GET",
557
+ `/v1/credits/ledger${toQuery({ customer, ...p })}`,
558
+ void 0,
559
+ options
560
+ ),
561
+ params
562
+ )
563
+ },
564
+ usage: {
565
+ /**
566
+ * A Customer's credit consumption (by external id) per UTC day, by Credit Action or
567
+ * category, over at most 92 days (the 30 ending today by default).
568
+ */
569
+ get: (customer, params = {}, options) => apiRequest(
570
+ ctx,
571
+ "GET",
572
+ `/v1/credits/usage${toQuery({ customer, ...params })}`,
573
+ void 0,
574
+ options
575
+ )
576
+ }
577
+ };
578
+ }
579
+
580
+ // src/resources/customers.ts
581
+ function createCustomersResource(ctx) {
582
+ return {
583
+ create: (input) => apiRequest(ctx, "POST", "/v1/customers", input),
584
+ get: (externalId, options) => apiRequest(
585
+ ctx,
586
+ "GET",
587
+ `/v1/customers/${encodeURIComponent(externalId)}`,
588
+ void 0,
589
+ options
590
+ ),
591
+ /** List customers (`q` searches external id / email / name). Auto-paginating: `await` the
592
+ * first page, `for await (…)` every page, or `.listAll()` to collect them. */
593
+ list: (params = {}, options) => makeListPromise(
594
+ (p) => apiRequest(
595
+ ctx,
596
+ "GET",
597
+ `/v1/customers${toQuery(p)}`,
598
+ void 0,
599
+ options
600
+ ),
601
+ params
602
+ ),
603
+ /** Full per-customer view: profile + subscriptions + charges + cards + balances. */
604
+ overview: (externalId, options) => apiRequest(
605
+ ctx,
606
+ "GET",
607
+ `/v1/customers/${encodeURIComponent(externalId)}/overview`,
608
+ void 0,
609
+ options
610
+ ),
611
+ paymentMethods: {
612
+ list: (customer, options) => apiRequest(
613
+ ctx,
614
+ "GET",
615
+ `/v1/customers/${encodeURIComponent(customer)}/payment-methods`,
616
+ void 0,
617
+ options
618
+ ).then((r) => r.paymentMethods),
619
+ setDefault: (customer, id) => apiRequest(
620
+ ctx,
621
+ "POST",
622
+ `/v1/customers/${encodeURIComponent(customer)}/payment-methods/${encodeURIComponent(id)}/default`
623
+ ),
624
+ remove: (customer, id) => apiRequest(
625
+ ctx,
626
+ "DELETE",
627
+ `/v1/customers/${encodeURIComponent(customer)}/payment-methods/${encodeURIComponent(id)}`
628
+ )
629
+ },
630
+ /** Export all portable customer data held by Billow. Secret-key only. */
631
+ exportData: (externalId, options) => apiRequest(
632
+ ctx,
633
+ "GET",
634
+ `/v1/customers/${encodeURIComponent(externalId)}/export`,
635
+ void 0,
636
+ options
637
+ ),
638
+ /** Anonymize PII after access-granting subscriptions have ended. Secret-key only. */
639
+ anonymize: (externalId, options) => apiRequest(
640
+ ctx,
641
+ "DELETE",
642
+ `/v1/customers/${encodeURIComponent(externalId)}`,
643
+ void 0,
644
+ options
645
+ )
646
+ };
647
+ }
648
+
649
+ // src/resources/deliverability.ts
650
+ function createDeliverabilityResource(ctx) {
651
+ return {
652
+ /** Email deliveries (newest first), optionally filtered by status. Auto-paginating. */
653
+ emails: (params = {}, options) => makeListPromise(
654
+ (p) => apiRequest(
655
+ ctx,
656
+ "GET",
657
+ `/v1/deliverability/emails${toQuery(p)}`,
658
+ void 0,
659
+ options
660
+ ),
661
+ params
662
+ ),
663
+ /** Outbound events (newest first), optionally filtered by status. Auto-paginating. */
664
+ events: (params = {}, options) => makeListPromise(
665
+ (p) => apiRequest(
666
+ ctx,
667
+ "GET",
668
+ `/v1/deliverability/events${toQuery(p)}`,
669
+ void 0,
670
+ options
671
+ ),
672
+ params
673
+ ),
674
+ /** Inbound provider webhooks (newest first), optionally filtered by outcome. Auto-paginating. */
675
+ inbound: (params = {}, options) => makeListPromise(
676
+ (p) => apiRequest(
677
+ ctx,
678
+ "GET",
679
+ `/v1/deliverability/inbound${toQuery(p)}`,
680
+ void 0,
681
+ options
682
+ ),
683
+ params
684
+ )
685
+ };
686
+ }
687
+
688
+ // src/resources/invoices.ts
689
+ function createInvoicesResource(ctx) {
690
+ return {
691
+ /** List invoices. Filters: status, customer (external id), from, to. Auto-paginating. */
692
+ list: (params = {}, options) => makeListPromise(
693
+ (p) => apiRequest(
694
+ ctx,
695
+ "GET",
696
+ `/v1/invoices${toQuery(p)}`,
697
+ void 0,
698
+ options
699
+ ),
700
+ params
701
+ ),
702
+ /** Fetch an invoice with its line items. */
703
+ get: (id, options) => apiRequest(
704
+ ctx,
705
+ "GET",
706
+ `/v1/invoices/${encodeURIComponent(id)}`,
707
+ void 0,
708
+ options
709
+ ),
710
+ /**
711
+ * Record that an open invoice was paid outside the gateway (bank transfer, cash, cheque).
712
+ * Settles the invoice and advances the subscription exactly like a gateway payment.
713
+ * `amount` must equal the invoice total. Operator (secret key) only.
714
+ */
715
+ recordPayment: (id, input) => apiRequest(
716
+ ctx,
717
+ "POST",
718
+ `/v1/invoices/${encodeURIComponent(id)}/record-payment`,
719
+ input
720
+ ),
721
+ /** Open or resume hosted checkout for an existing outstanding invoice. */
722
+ pay: (id, input = {}, options) => apiRequest(
723
+ ctx,
724
+ "POST",
725
+ `/v1/invoices/${encodeURIComponent(id)}/pay`,
726
+ input,
727
+ options
728
+ ),
729
+ /** Download the invoice as a PDF (raw bytes). */
730
+ pdf: (id, options) => apiRequestBinary(ctx, "GET", `/v1/invoices/${encodeURIComponent(id)}/pdf`, options),
731
+ /** Download the invoice's payment receipt as a PDF (raw bytes; 404 if unpaid). */
732
+ receipt: (id, options) => apiRequestBinary(ctx, "GET", `/v1/invoices/${encodeURIComponent(id)}/receipt.pdf`, options)
733
+ };
734
+ }
735
+
736
+ // src/resources/marketplace.ts
737
+ function createMarketplaceResource(ctx) {
738
+ return {
739
+ /** Every available Integration with this tenant's install state (no secret values). */
740
+ catalog: (options) => apiRequest(
741
+ ctx,
742
+ "GET",
743
+ "/v1/marketplace",
744
+ void 0,
745
+ options
746
+ ).then((r) => r.catalog),
747
+ /** One catalog entry by slug. */
748
+ get: (slug, options) => apiRequest(
749
+ ctx,
750
+ "GET",
751
+ `/v1/marketplace/${encodeURIComponent(slug)}`,
752
+ void 0,
753
+ options
754
+ ).then((r) => r.item),
755
+ /** Install or reconfigure an Integration; `values` are the manifest form's field values. */
756
+ upsert: (slug, values) => apiRequest(ctx, "PUT", `/v1/marketplace/${encodeURIComponent(slug)}`, {
757
+ values
758
+ }).then((r) => r.item),
759
+ /** Enable or disable an installed Integration. */
760
+ setEnabled: (slug, enabled) => apiRequest(
761
+ ctx,
762
+ "POST",
763
+ `/v1/marketplace/${encodeURIComponent(slug)}/enabled`,
764
+ { enabled }
765
+ ).then((r) => r.item),
766
+ /** Uninstall an Integration. */
767
+ remove: (slug) => apiRequest(
768
+ ctx,
769
+ "DELETE",
770
+ `/v1/marketplace/${encodeURIComponent(slug)}`
771
+ ),
772
+ /**
773
+ * Verify an installed Integration's config. An email Integration sends a test
774
+ * message to `to` (returns `{ sent, to }`); an accounting Integration runs a
775
+ * non-mutating connectivity check with no recipient (returns `{ ok: true }`).
776
+ */
777
+ test: (slug, to) => apiRequest(
778
+ ctx,
779
+ "POST",
780
+ `/v1/marketplace/${encodeURIComponent(slug)}/test`,
781
+ to ? { to } : {}
782
+ )
783
+ };
784
+ }
785
+
786
+ // src/resources/metrics.ts
787
+ function createMetricsResource(ctx) {
788
+ return {
789
+ /** KPI cards for a currency over a window (`from`/`to` default to the last 30 days). */
790
+ overview: (params = {}, options) => apiRequest(
791
+ ctx,
792
+ "GET",
793
+ `/v1/metrics/overview${toQuery(params)}`,
794
+ void 0,
795
+ options
796
+ ),
797
+ /** A single bucketed metric (revenue / mrr / subs / success rate) over a window. */
798
+ timeseries: (params = {}, options) => apiRequest(
799
+ ctx,
800
+ "GET",
801
+ `/v1/metrics/timeseries${toQuery(params)}`,
802
+ void 0,
803
+ options
804
+ ),
805
+ /**
806
+ * KPIs rolled up into the org's reporting currency across all billing currencies
807
+ * (Phase H). Resolves to `null` when no reporting currency is configured.
808
+ */
809
+ rollup: (params = {}, options) => apiRequest(
810
+ ctx,
811
+ "GET",
812
+ `/v1/metrics/rollup${toQuery(params)}`,
813
+ void 0,
814
+ options
815
+ ).then((r) => r.rollup)
816
+ };
817
+ }
818
+
819
+ // src/resources/portal-sessions.ts
820
+ function createPortalSessionsResource(ctx) {
821
+ return {
822
+ create: (input) => apiRequest(ctx, "POST", "/v1/portal-sessions", input)
823
+ };
824
+ }
825
+
826
+ // src/resources/products.ts
827
+ function createProductsResource(ctx) {
828
+ return {
829
+ /** Define a product (its first active version + prices + entitlements). */
830
+ create: (input) => apiRequest(ctx, "POST", "/v1/products", input),
831
+ /**
832
+ * Update a product: rename, archive/unarchive, and/or edit its prices +
833
+ * entitlements in place.
834
+ */
835
+ update: (slug, input) => apiRequest(ctx, "PATCH", `/v1/products/${encodeURIComponent(slug)}`, input),
836
+ /** Fetch a product (active version + prices + entitlements) by slug. */
837
+ get: (slug, options) => apiRequest(
838
+ ctx,
839
+ "GET",
840
+ `/v1/products/${encodeURIComponent(slug)}`,
841
+ void 0,
842
+ options
843
+ ),
844
+ /** List products. `includeArchived` also returns archived ones (for admin views). */
845
+ list: (params = {}, options) => apiRequest(
846
+ ctx,
847
+ "GET",
848
+ `/v1/products${params.includeArchived ? "?includeArchived=true" : ""}`,
849
+ void 0,
850
+ options
851
+ ).then((r) => r.products),
852
+ /** Delete a plan (soft-delete, ADR-0015). */
853
+ delete: (slug) => apiRequest(ctx, "DELETE", `/v1/products/${encodeURIComponent(slug)}`),
854
+ /** Migrate every live subscriber of `fromSlug` onto `toSlug`. */
855
+ migrateSubscribers: (fromSlug, toSlug) => apiRequest(
856
+ ctx,
857
+ "POST",
858
+ `/v1/products/${encodeURIComponent(fromSlug)}/migrate-subscribers`,
859
+ { toSlug }
860
+ ),
861
+ /** Live-subscriber count per product, keyed by product id. */
862
+ subscriberCounts: (options) => apiRequest(
863
+ ctx,
864
+ "GET",
865
+ "/v1/products/subscriber-counts",
866
+ void 0,
867
+ options
868
+ ).then((r) => r.counts)
869
+ };
870
+ }
871
+
872
+ // src/resources/settings.ts
873
+ function createSettingsResource(ctx) {
874
+ return {
875
+ /** The dunning retry schedule: configured gaps in days (null = default), + the default. */
876
+ dunning: {
877
+ get: (options) => apiRequest(
878
+ ctx,
879
+ "GET",
880
+ "/v1/settings/dunning",
881
+ void 0,
882
+ options
883
+ ),
884
+ /** Set the retry gaps (whole days); pass `null`/`[]` to reset to the default. */
885
+ update: (retryDays) => apiRequest(
886
+ ctx,
887
+ "PUT",
888
+ "/v1/settings/dunning",
889
+ { retryDays }
890
+ )
891
+ },
892
+ /**
893
+ * Usage settlement grace (hours): defers *collection* of a boundary-adjacent priced
894
+ * calendar-meter renewal past the tz month boundary until late usage settles — the billed
895
+ * windows/amount are unchanged, only the invoice timing shifts. `0` disables it (the default);
896
+ * a no-op for any subscription without a priced calendar meter. Cap 72h.
897
+ */
898
+ usageSettlementGrace: {
899
+ get: (options) => apiRequest(
900
+ ctx,
901
+ "GET",
902
+ "/v1/settings/usage-settlement-grace",
903
+ void 0,
904
+ options
905
+ ),
906
+ /** Set the grace window in whole hours (0–72); `0` disables. */
907
+ update: (graceHours) => apiRequest(ctx, "PUT", "/v1/settings/usage-settlement-grace", {
908
+ graceHours
909
+ })
910
+ },
911
+ /** The currency cross-currency reporting rolls up into (null = none configured). */
912
+ reporting: {
913
+ get: (options) => apiRequest(
914
+ ctx,
915
+ "GET",
916
+ "/v1/settings/reporting",
917
+ void 0,
918
+ options
919
+ ),
920
+ /** Set the reporting currency (3-letter ISO), or `null` to clear it. */
921
+ update: (currency) => apiRequest(ctx, "PUT", "/v1/settings/reporting", {
922
+ currency
923
+ })
924
+ },
925
+ /** The manually-maintained FX rates the per-invoice reporting snapshot draws on. */
926
+ fxRates: {
927
+ list: (options) => apiRequest(
928
+ ctx,
929
+ "GET",
930
+ "/v1/settings/fx-rates",
931
+ void 0,
932
+ options
933
+ ).then((r) => r.rates),
934
+ /** Upsert one `base → quote` rate (`rate` is the decimal quote-per-base, > 0). */
935
+ set: (baseCurrency, quoteCurrency, rate) => apiRequest(ctx, "PUT", "/v1/settings/fx-rates", {
936
+ baseCurrency,
937
+ quoteCurrency,
938
+ rate
939
+ }),
940
+ /** Remove a `base → quote` rate. */
941
+ remove: (baseCurrency, quoteCurrency) => apiRequest(
942
+ ctx,
943
+ "DELETE",
944
+ `/v1/settings/fx-rates?base=${encodeURIComponent(baseCurrency)}&quote=${encodeURIComponent(quoteCurrency)}`
945
+ )
946
+ },
947
+ /** Transactional email templates (PRD-10): per-template subject/message overrides. */
948
+ emailTemplates: {
949
+ /** Every template, merged with this project's overrides, in display order. */
950
+ list: (options) => apiRequest(
951
+ ctx,
952
+ "GET",
953
+ "/v1/settings/email-templates",
954
+ void 0,
955
+ options
956
+ ).then((r) => r.templates),
957
+ /** Update one template's override (validated against its variable allowlist). */
958
+ update: (key, input) => apiRequest(
959
+ ctx,
960
+ "PUT",
961
+ `/v1/settings/email-templates/${encodeURIComponent(key)}`,
962
+ input
963
+ ).then((r) => r.template),
964
+ /**
965
+ * Send a `[Test]`-prefixed sample of one template to `to`. Dashboard-only:
966
+ * the API rejects developer keys (the dashboard restricts the recipient to
967
+ * the signed-in operator; a developer key has no such guarantee).
968
+ */
969
+ test: (key, to) => apiRequest(
970
+ ctx,
971
+ "POST",
972
+ `/v1/settings/email-templates/${encodeURIComponent(key)}/test`,
973
+ { to }
974
+ )
975
+ }
976
+ };
977
+ }
978
+
979
+ // src/resources/subscriptions.ts
980
+ function createSubscriptionsResource(ctx) {
981
+ return {
982
+ /** Start a subscription -> returns the subscription plus a hosted checkout URL. `productId`
983
+ * accepts the product's id or slug. Pass an `idempotencyKey` to make it safe to retry. */
984
+ create: (input, options) => apiRequest(ctx, "POST", "/v1/subscriptions", input, options),
985
+ /** Grant a free comp subscription (operator action). `productId` accepts the product's id or slug. */
986
+ comp: (input) => apiRequest(ctx, "POST", "/v1/subscriptions", {
987
+ ...input,
988
+ comp: true
989
+ }),
990
+ /** List subscriptions (paginated). Filters: status, customer (external id). */
991
+ list: (params = {}, options) => makeListPromise(
992
+ (p) => apiRequest(
993
+ ctx,
994
+ "GET",
995
+ `/v1/subscriptions${toQuery(p)}`,
996
+ void 0,
997
+ options
998
+ ),
999
+ params
1000
+ ),
1001
+ get: (id, options) => apiRequest(
1002
+ ctx,
1003
+ "GET",
1004
+ `/v1/subscriptions/${encodeURIComponent(id)}`,
1005
+ void 0,
1006
+ options
1007
+ ),
1008
+ /**
1009
+ * The single subscription that currently determines the customer's access, resolved by
1010
+ * billow's own rule (access-granting statuses first, then newest) - or `null` if the
1011
+ * customer has none. Prefer this over picking from `list()` yourself: it avoids the
1012
+ * "newer failed-checkout subscription shadows an older active one" selection bug.
1013
+ */
1014
+ current: (params, options) => apiRequest(
1015
+ ctx,
1016
+ "GET",
1017
+ `/v1/subscriptions/current${toQuery(params)}`,
1018
+ void 0,
1019
+ options
1020
+ ),
1021
+ /** Cancel — immediately, or at period end with `{ atPeriodEnd: true }`. */
1022
+ cancel: (id, opts) => apiRequest(
1023
+ ctx,
1024
+ "POST",
1025
+ `/v1/subscriptions/${encodeURIComponent(id)}/cancel`,
1026
+ opts ?? {}
1027
+ ),
1028
+ /** Reverse a scheduled period-end cancellation. Duplicate calls are safe. */
1029
+ resumeCancellation: (id) => apiRequest(
1030
+ ctx,
1031
+ "POST",
1032
+ `/v1/subscriptions/${encodeURIComponent(id)}/resume-cancellation`
1033
+ ),
1034
+ pause: (id) => apiRequest(ctx, "POST", `/v1/subscriptions/${encodeURIComponent(id)}/pause`),
1035
+ resume: (id) => apiRequest(ctx, "POST", `/v1/subscriptions/${encodeURIComponent(id)}/resume`),
1036
+ /** Apply a coupon to an existing subscription. */
1037
+ applyCoupon: (id, code) => apiRequest(
1038
+ ctx,
1039
+ "POST",
1040
+ `/v1/subscriptions/${encodeURIComponent(id)}/coupon`,
1041
+ { code }
1042
+ ),
1043
+ /** Change plan — immediate prorated upgrade, or downgrade scheduled for period end. `productId` accepts the product's id or slug. */
1044
+ changePlan: (id, productId) => apiRequest(ctx, "POST", `/v1/subscriptions/${encodeURIComponent(id)}/change-plan`, { productId }),
1045
+ /** Clear a pending downgrade. Duplicate calls are safe. */
1046
+ clearScheduledPlanChange: (id) => apiRequest(
1047
+ ctx,
1048
+ "DELETE",
1049
+ `/v1/subscriptions/${encodeURIComponent(id)}/change-plan`
1050
+ ),
1051
+ items: {
1052
+ /** List a subscription's items (base + add-ons). */
1053
+ list: (id, options) => apiRequest(
1054
+ ctx,
1055
+ "GET",
1056
+ `/v1/subscriptions/${encodeURIComponent(id)}/items`,
1057
+ void 0,
1058
+ options
1059
+ ).then((r) => r.items),
1060
+ /** Attach an add-on Product. `productId` accepts the product's id or slug. */
1061
+ add: (id, productId, quantity) => apiRequest(
1062
+ ctx,
1063
+ "POST",
1064
+ `/v1/subscriptions/${encodeURIComponent(id)}/items`,
1065
+ { productId, ...quantity != null ? { quantity } : {} }
1066
+ ),
1067
+ /** Change an item's quantity (seats). */
1068
+ updateQuantity: (id, itemId, quantity) => apiRequest(
1069
+ ctx,
1070
+ "PATCH",
1071
+ `/v1/subscriptions/${encodeURIComponent(id)}/items/${encodeURIComponent(itemId)}`,
1072
+ { quantity }
1073
+ ),
1074
+ /** Detach an add-on item. */
1075
+ remove: (id, itemId) => apiRequest(
1076
+ ctx,
1077
+ "DELETE",
1078
+ `/v1/subscriptions/${encodeURIComponent(id)}/items/${encodeURIComponent(itemId)}`
1079
+ )
1080
+ }
1081
+ };
1082
+ }
1083
+
1084
+ // src/resources/webhook-endpoints.ts
1085
+ function createWebhookEndpointsResource(ctx) {
1086
+ return {
1087
+ /** Register an endpoint. The signing secret is returned once. */
1088
+ create: (input) => apiRequest(ctx, "POST", "/v1/webhook-endpoints", input),
1089
+ /** List endpoints (newest first; secrets are never returned). Auto-paginating. */
1090
+ list: (params = {}, options) => makeListPromise(
1091
+ (p) => apiRequest(
1092
+ ctx,
1093
+ "GET",
1094
+ `/v1/webhook-endpoints${toQuery(p)}`,
1095
+ void 0,
1096
+ options
1097
+ ),
1098
+ params
1099
+ ),
1100
+ /** Fetch one endpoint by id. */
1101
+ get: (id, options) => apiRequest(
1102
+ ctx,
1103
+ "GET",
1104
+ `/v1/webhook-endpoints/${encodeURIComponent(id)}`,
1105
+ void 0,
1106
+ options
1107
+ ),
1108
+ /** Update an endpoint's URL, enabled flag, or event filter. */
1109
+ update: (id, input) => apiRequest(
1110
+ ctx,
1111
+ "PATCH",
1112
+ `/v1/webhook-endpoints/${encodeURIComponent(id)}`,
1113
+ input
1114
+ ),
1115
+ /** Delete an endpoint and its delivery history. */
1116
+ delete: (id) => apiRequest(ctx, "DELETE", `/v1/webhook-endpoints/${encodeURIComponent(id)}`),
1117
+ /** Rotate the signing secret — the new plaintext is returned once. */
1118
+ rotateSecret: (id) => apiRequest(
1119
+ ctx,
1120
+ "POST",
1121
+ `/v1/webhook-endpoints/${encodeURIComponent(id)}/rotate-secret`
1122
+ ),
1123
+ /** List delivery attempts for an endpoint (newest first). Auto-paginating. */
1124
+ deliveries: (id, params = {}, options) => makeListPromise(
1125
+ (p) => apiRequest(
1126
+ ctx,
1127
+ "GET",
1128
+ `/v1/webhook-endpoints/${encodeURIComponent(id)}/deliveries${toQuery(p)}`,
1129
+ void 0,
1130
+ options
1131
+ ),
1132
+ params
1133
+ )
1134
+ };
1135
+ }
1136
+
1137
+ // src/index.ts
1138
+ var Billow = class {
1139
+ #ctx;
1140
+ customers;
1141
+ charges;
1142
+ checkoutMethods;
1143
+ /** Prepaid credits: grant them to a Customer (quantities are decimal strings of microcredits). */
1144
+ credits;
1145
+ invoices;
1146
+ metrics;
1147
+ webhookEndpoints;
1148
+ products;
1149
+ subscriptions;
1150
+ portalSessions;
1151
+ deliverability;
1152
+ marketplace;
1153
+ /**
1154
+ * Organization settings (Phase D3, F) — the configurable dunning schedule, usage settlement grace,
1155
+ * and the cross-currency reporting currency + FX-rate registry (ADR-0013).
1156
+ */
1157
+ settings;
1158
+ constructor(apiKey, opts = {}) {
1159
+ if (!apiKey) throw new Error("billow: an API key is required");
1160
+ this.#ctx = makeContext(apiKey, opts);
1161
+ this.customers = createCustomersResource(this.#ctx);
1162
+ this.charges = createChargesResource(this.#ctx);
1163
+ this.checkoutMethods = createCheckoutMethodsResource(this.#ctx);
1164
+ this.credits = createCreditsResource(this.#ctx);
1165
+ this.invoices = createInvoicesResource(this.#ctx);
1166
+ this.metrics = createMetricsResource(this.#ctx);
1167
+ this.webhookEndpoints = createWebhookEndpointsResource(this.#ctx);
1168
+ this.products = createProductsResource(this.#ctx);
1169
+ this.subscriptions = createSubscriptionsResource(this.#ctx);
1170
+ this.portalSessions = createPortalSessionsResource(this.#ctx);
1171
+ this.deliverability = createDeliverabilityResource(this.#ctx);
1172
+ this.marketplace = createMarketplaceResource(this.#ctx);
1173
+ this.settings = createSettingsResource(this.#ctx);
1174
+ }
1175
+ #request(method, path, body, options) {
1176
+ return apiRequest(this.#ctx, method, path, body, options);
1177
+ }
1178
+ coupons = {
1179
+ /** Define a coupon (a reusable discount template). */
1180
+ create: (input) => this.#request("POST", "/v1/coupons", input),
1181
+ /** List coupons (secret key only — enumerates live promo codes). Auto-paginating. */
1182
+ list: (params = {}, options) => makeListPromise(
1183
+ (p) => this.#request("GET", `/v1/coupons${toQuery(p)}`, void 0, options),
1184
+ params
1185
+ ),
1186
+ /** Deactivate a coupon (existing discounts keep running). */
1187
+ deactivate: (id) => this.#request("POST", `/v1/coupons/${encodeURIComponent(id)}/deactivate`)
1188
+ };
1189
+ /** Manage developer webhook endpoints — register, rotate secrets, inspect deliveries.
1190
+ * (To VERIFY incoming deliveries, import `constructEvent` from `@usebillow/sdk/webhooks`.) */
1191
+ features = {
1192
+ /** Define a feature (a boolean access gate, or a metered feature with a meter). */
1193
+ create: (input) => this.#request("POST", "/v1/features", input),
1194
+ /**
1195
+ * Edit a feature in place: its `name`, and — for a metered feature — its `meter`
1196
+ * aggregation. The server refuses a meter change once usage has been recorded (it
1197
+ * would rewrite billed history), so set the meter right at create time or before you
1198
+ * start tracking. Returns the updated feature.
1199
+ */
1200
+ update: (slug, input) => this.#request("PATCH", `/v1/features/${encodeURIComponent(slug)}`, input),
1201
+ /** List features in the tenant. Auto-paginating. */
1202
+ list: (params = {}, options) => makeListPromise(
1203
+ (p) => this.#request("GET", `/v1/features${toQuery(p)}`, void 0, options),
1204
+ params
1205
+ ),
1206
+ /**
1207
+ * Usage Alerts + Spend caps on a metered feature (Phase C). A `threshold`
1208
+ * Alert notifies (webhook + optional customer email) as usage crosses its
1209
+ * thresholds; a `cap` blocks `check` once reached.
1210
+ */
1211
+ alerts: {
1212
+ /** List the alerts + spend cap on a metered feature (by slug). */
1213
+ list: (feature, options) => this.#request(
1214
+ "GET",
1215
+ `/v1/features/${encodeURIComponent(feature)}/alerts`,
1216
+ void 0,
1217
+ options
1218
+ ).then((r) => r.alerts),
1219
+ /** Define an Alert or Spend cap on a metered feature. */
1220
+ create: (feature, input) => this.#request(
1221
+ "POST",
1222
+ `/v1/features/${encodeURIComponent(feature)}/alerts`,
1223
+ input
1224
+ ),
1225
+ /** Delete one alert/cap rule by id. */
1226
+ remove: (feature, id) => this.#request(
1227
+ "DELETE",
1228
+ `/v1/features/${encodeURIComponent(feature)}/alerts/${encodeURIComponent(id)}`
1229
+ )
1230
+ }
1231
+ };
1232
+ integrations = {
1233
+ /** The registered payment providers + their config manifests (drives the picker + form). */
1234
+ providers: (options) => this.#request(
1235
+ "GET",
1236
+ "/v1/integrations/providers",
1237
+ void 0,
1238
+ options
1239
+ ).then((r) => r.providers),
1240
+ /** List configured credential sets per currency (provider + non-secret config). Secret-only. */
1241
+ list: (options) => this.#request(
1242
+ "GET",
1243
+ "/v1/integrations",
1244
+ void 0,
1245
+ options
1246
+ ).then((r) => r.integrations),
1247
+ /**
1248
+ * Verify a provider's configuration against its live API. Pass `currency` to
1249
+ * choose a credential set and `target` to probe one verify target (Paymob: a
1250
+ * method); pass `sampleToken` (SANDBOX ONLY) to run a functional sub-test.
1251
+ * This probe hits the provider's live API (mutates nothing here), so it takes a
1252
+ * per-call {@link CallOptions} for a timeout / cancellation — `opts` is spread into
1253
+ * the body, so `options` stays a separate trailing arg (never merged in).
1254
+ */
1255
+ verify: (provider, opts, options) => this.#request(
1256
+ "POST",
1257
+ "/v1/integrations/verify",
1258
+ { provider, ...opts },
1259
+ options
1260
+ ),
1261
+ /** Manage stored payment-provider credentials (the dashboard's "manage credentials" forms). */
1262
+ credentials: {
1263
+ /** The editable view of stored credential sets (secrets are never returned). */
1264
+ list: (options) => this.#request(
1265
+ "GET",
1266
+ "/v1/integrations/credentials",
1267
+ void 0,
1268
+ options
1269
+ ).then((r) => r.credentials),
1270
+ /** Create or rotate a (provider, currency) credential set. */
1271
+ upsert: (input) => this.#request("PUT", "/v1/integrations/credentials", input),
1272
+ /** Delete a (provider, currency) credential set. */
1273
+ remove: (provider, currency) => this.#request(
1274
+ "DELETE",
1275
+ `/v1/integrations/credentials/${encodeURIComponent(provider)}/${encodeURIComponent(currency)}`
1276
+ )
1277
+ }
1278
+ };
1279
+ /** The merchant's business identity — the seller block on documents and email from-name. */
1280
+ businessProfile = {
1281
+ /** The stored profile, or null if one was never saved. */
1282
+ get: (options) => this.#request(
1283
+ "GET",
1284
+ "/v1/business-profile",
1285
+ void 0,
1286
+ options
1287
+ ).then((r) => r.profile),
1288
+ /** Create or update the profile; omit a field to keep it, send "" to clear it. */
1289
+ update: (input) => this.#request("PUT", "/v1/business-profile", input).then(
1290
+ (r) => r.profile
1291
+ )
1292
+ };
1293
+ /**
1294
+ * Hosted customer surfaces (Phase G, ADR-0014). Mint a portal session for one of
1295
+ * your signed-in users and redirect them to the returned `url` — the self-serve
1296
+ * portal, or a `checkout` hand-off for `productId`. The Customer's own calls go
1297
+ * through {@link BillowPortal}, constructed with the session token.
1298
+ */
1299
+ /** Identity of this key's tenant — org, environment, configured currencies. */
1300
+ me(options) {
1301
+ return this.#request("GET", "/v1/me", void 0, options);
1302
+ }
1303
+ /**
1304
+ * Global search (PRD-12) across customers, invoices, subscriptions, and charges
1305
+ * for the key's `(project, environment)`. Bounded per entity (default 5, max 10);
1306
+ * a blank query returns empty results. Backs the dashboard's ⌘K palette.
1307
+ */
1308
+ search(q, limit, options) {
1309
+ return this.#request(
1310
+ "GET",
1311
+ `/v1/search${toQuery({ q, limit })}`,
1312
+ void 0,
1313
+ options
1314
+ );
1315
+ }
1316
+ // ── The headline verbs ──────────────────────────────────────────────
1317
+ /**
1318
+ * Gate access to a feature. `featureId` is the feature slug. Returns
1319
+ * `{ allowed, balance }` — `balance` is `null` for unlimited/boolean features.
1320
+ */
1321
+ check(input, options) {
1322
+ return this.#request("POST", "/v1/check", input, options);
1323
+ }
1324
+ /**
1325
+ * Record usage of a metered feature (`value` defaults to 1; negative credits
1326
+ * back). Pass `idempotencyKey` to make a retried call a no-op — which also makes the call
1327
+ * safe to retry automatically when `maxRetries` is set.
1328
+ */
1329
+ track(input, options) {
1330
+ return this.#request(
1331
+ "POST",
1332
+ "/v1/track",
1333
+ input,
1334
+ input.idempotencyKey ? { ...options, idempotencyKey: input.idempotencyKey } : options
1335
+ );
1336
+ }
1337
+ /** List a customer's live entitlements (one per feature). */
1338
+ entitlements(customerId, options) {
1339
+ return this.#request(
1340
+ "GET",
1341
+ `/v1/customers/${encodeURIComponent(customerId)}/entitlements`,
1342
+ void 0,
1343
+ options
1344
+ );
1345
+ }
1346
+ /** Start a subscription → returns a hosted checkout URL to redirect the customer to. */
1347
+ attach(input, options) {
1348
+ return this.#request("POST", "/v1/attach", input, options);
1349
+ }
1350
+ };
1351
+ var BillowPublishable = class {
1352
+ #ctx;
1353
+ constructor(publishableKey, opts = {}) {
1354
+ if (!publishableKey) throw new Error("billow: a publishable key is required");
1355
+ if (!publishableKey.includes("_pk_")) {
1356
+ throw new Error(
1357
+ "billow: BillowPublishable needs a publishable key (bl_<env>_pk_\u2026), not a secret key"
1358
+ );
1359
+ }
1360
+ this.#ctx = makeContext(publishableKey, opts);
1361
+ }
1362
+ products = {
1363
+ /** The active catalog — products and their prices, for a pricing table. */
1364
+ list: (options) => apiRequest(
1365
+ this.#ctx,
1366
+ "GET",
1367
+ "/v1/products",
1368
+ void 0,
1369
+ options
1370
+ ).then((r) => r.products),
1371
+ /** Fetch one active product (with its prices) by slug. */
1372
+ get: (slug, options) => apiRequest(
1373
+ this.#ctx,
1374
+ "GET",
1375
+ `/v1/products/${encodeURIComponent(slug)}`,
1376
+ void 0,
1377
+ options
1378
+ )
1379
+ };
1380
+ };
1381
+ var BillowPortal = class {
1382
+ #ctx;
1383
+ constructor(sessionToken, opts = {}) {
1384
+ if (!sessionToken) throw new Error("billow: a portal session token is required");
1385
+ this.#ctx = makeContext(sessionToken, opts);
1386
+ }
1387
+ /** The portal shell: flow, return URL, merchant brand, and customer identity —
1388
+ * the lightweight payload the hosting app frames every page with, and the
1389
+ * validate-and-route check at login. */
1390
+ session(options) {
1391
+ return apiRequest(this.#ctx, "GET", "/portal/session", void 0, options);
1392
+ }
1393
+ /** The self-serve home payload: identity + subscriptions + usage + saved cards. */
1394
+ me(options) {
1395
+ return apiRequest(this.#ctx, "GET", "/portal/me", void 0, options);
1396
+ }
1397
+ invoices = {
1398
+ /** The Customer's invoices (newest first). Auto-paginating. */
1399
+ list: (params = {}, options) => makeListPromise(
1400
+ (p) => apiRequest(
1401
+ this.#ctx,
1402
+ "GET",
1403
+ `/portal/invoices${toQuery(p)}`,
1404
+ void 0,
1405
+ options
1406
+ ),
1407
+ params
1408
+ ),
1409
+ /** Open or resume checkout for this customer's outstanding invoice. */
1410
+ pay: (id, input = {}, options) => apiRequest(
1411
+ this.#ctx,
1412
+ "POST",
1413
+ `/portal/invoices/${encodeURIComponent(id)}/pay`,
1414
+ input,
1415
+ options
1416
+ ),
1417
+ /** Download one of the Customer's invoices as a PDF (raw bytes). */
1418
+ pdf: (id, options) => apiRequestBinary(this.#ctx, "GET", `/portal/invoices/${encodeURIComponent(id)}/pdf`, options),
1419
+ /** Download the payment receipt for one of the Customer's invoices (raw bytes). */
1420
+ receiptPdf: (id, options) => apiRequestBinary(
1421
+ this.#ctx,
1422
+ "GET",
1423
+ `/portal/invoices/${encodeURIComponent(id)}/receipt.pdf`,
1424
+ options
1425
+ )
1426
+ };
1427
+ subscriptions = {
1428
+ /** Schedule one of the Customer's subscriptions to cancel at period end. */
1429
+ cancel: (id) => apiRequest(
1430
+ this.#ctx,
1431
+ "POST",
1432
+ `/portal/subscriptions/${encodeURIComponent(id)}/cancel`
1433
+ ),
1434
+ /** Reverse a scheduled period-end cancellation. */
1435
+ resumeCancellation: (id) => apiRequest(
1436
+ this.#ctx,
1437
+ "POST",
1438
+ `/portal/subscriptions/${encodeURIComponent(id)}/resume-cancellation`
1439
+ ),
1440
+ /** Move one of the Customer's subscriptions to another plan (upgrade now / downgrade scheduled). */
1441
+ changePlan: (id, productId) => apiRequest(
1442
+ this.#ctx,
1443
+ "POST",
1444
+ `/portal/subscriptions/${encodeURIComponent(id)}/plan`,
1445
+ { productId }
1446
+ ),
1447
+ /** Clear a pending downgrade. */
1448
+ clearScheduledPlanChange: (id) => apiRequest(
1449
+ this.#ctx,
1450
+ "DELETE",
1451
+ `/portal/subscriptions/${encodeURIComponent(id)}/plan`
1452
+ )
1453
+ };
1454
+ paymentMethod = {
1455
+ /** Open a checkout to save or replace the Customer's card (zero-amount tokenization). */
1456
+ update: (returnUrl) => apiRequest(
1457
+ this.#ctx,
1458
+ "POST",
1459
+ "/portal/payment-method/update",
1460
+ returnUrl ? { returnUrl } : {}
1461
+ )
1462
+ };
1463
+ paymentMethods = {
1464
+ list: (options) => apiRequest(
1465
+ this.#ctx,
1466
+ "GET",
1467
+ "/portal/payment-methods",
1468
+ void 0,
1469
+ options
1470
+ ).then((r) => r.paymentMethods),
1471
+ setDefault: (id) => apiRequest(
1472
+ this.#ctx,
1473
+ "POST",
1474
+ `/portal/payment-methods/${encodeURIComponent(id)}/default`
1475
+ ),
1476
+ remove: (id) => apiRequest(
1477
+ this.#ctx,
1478
+ "DELETE",
1479
+ `/portal/payment-methods/${encodeURIComponent(id)}`
1480
+ )
1481
+ };
1482
+ /** The checkout hand-off, for a session minted with `flow: "checkout"`. */
1483
+ checkout = {
1484
+ /** The plan being confirmed (product + base price). */
1485
+ get: (options) => apiRequest(this.#ctx, "GET", "/portal/checkout", void 0, options),
1486
+ /** Subscribe to the plan → returns the hosted Paymob checkout URL to redirect to. */
1487
+ start: () => apiRequest(this.#ctx, "POST", "/portal/checkout")
1488
+ };
1489
+ };
1490
+
1491
+ export { BILLOW_ERROR_CODES, Billow, BillowApiError, BillowPortal, BillowPublishable, currencyExponent, formatMoney, isSecretField, toMajorUnits, toMinorUnits };
1492
+ //# sourceMappingURL=chunk-Z6VXPONT.js.map
1493
+ //# sourceMappingURL=chunk-Z6VXPONT.js.map