@usebillow/sdk 0.5.0 → 0.6.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.
@@ -0,0 +1,371 @@
1
+ import { A as ActivatedSubscriptionResponse, C as CreateSubscriptionResponse, P as PendingSubscriptionCheckoutResponse, a as ProductResponse, b as ProductEntitlementResponse, c as ProductPriceResponse, S as SubscriptionResponse, d as SubscriptionDetailResponse, e as SubscriptionItemResponse } from './index.d-CKQAhQfJ.js';
2
+ import { C as ChargeStatus } from './billing-status-BZQN_gm7.js';
3
+
4
+ /** Public SDK types for this domain. Re-exported by ../types.ts. */
5
+ interface BillowOptions {
6
+ /**
7
+ * Base URL of the Billow API. Falls back to the `BILLOW_URL` env var, then to the hosted
8
+ * API at `https://api.usebillow.com`. Set it for local development (for example
9
+ * `http://localhost:4001`) or a self-hosted server.
10
+ */
11
+ baseUrl?: string;
12
+ fetch?: typeof fetch;
13
+ /**
14
+ * Hard per-request timeout in milliseconds; the request is aborted once it elapses.
15
+ * Defaults to 60_000. Set `0` to disable the timeout.
16
+ */
17
+ timeoutMs?: number;
18
+ /**
19
+ * How many times to retry a transient failure. Retries are **opt-in** — the default is
20
+ * `0`. Only requests that are safe to repeat are retried: every `GET`, plus **any** method
21
+ * on a `429` (a rate-limited request never executed). A write (`POST`/`PATCH`/`DELETE`) is
22
+ * never retried on a `5xx`/network error, since it may have already applied.
23
+ */
24
+ maxRetries?: number;
25
+ /**
26
+ * Base backoff in milliseconds between retries — exponential (`base × 2^attempt`), and a
27
+ * `Retry-After` response header takes precedence when present. Defaults to 500.
28
+ */
29
+ retryBackoffMs?: number;
30
+ }
31
+ /**
32
+ * Per-call overrides passed as the last argument to a resource method.
33
+ * - `signal` cancels the request (a caller abort is surfaced immediately and never retried).
34
+ * - `timeoutMs` overrides the client's default timeout for this call (applied per attempt).
35
+ * - `idempotencyKey` sends an `Idempotency-Key` and makes the write **safe to retry** — reused
36
+ * verbatim on every attempt. Only exposed on writes the server replays (create/attach/track/refund).
37
+ */
38
+ interface RequestOptions {
39
+ signal?: AbortSignal;
40
+ timeoutMs?: number;
41
+ idempotencyKey?: string;
42
+ }
43
+ /** Options for writes that require a stable idempotency key. */
44
+ type IdempotentRequestOptions = RequestOptions & {
45
+ idempotencyKey: string;
46
+ };
47
+ /** Per-call overrides for reads and non-replayable writes — cancellation + timeout, no idempotency key. */
48
+ type CallOptions = Omit<RequestOptions, "idempotencyKey">;
49
+ interface Customer {
50
+ id: string;
51
+ externalId: string;
52
+ name?: string | null;
53
+ email?: string | null;
54
+ phone?: string | null;
55
+ /** The customer's tax / VAT registration number, recorded on their invoices. */
56
+ taxId?: string | null;
57
+ }
58
+ /** How a price's tax relates to its amount: added on top (`exclusive`) or contained (`inclusive`). */
59
+ type TaxBehavior = "exclusive" | "inclusive";
60
+ interface CheckResult {
61
+ feature: string;
62
+ allowed: boolean;
63
+ balance: number | null;
64
+ }
65
+ interface AttachResult {
66
+ /** Hosted checkout URL to redirect the customer to (null if none was minted). */
67
+ url: string | null;
68
+ /** Null until a paid first-cycle checkout succeeds and creates the subscription. */
69
+ subscriptionId: string | null;
70
+ checkoutSessionId: string | null;
71
+ }
72
+ type BillingInterval = "day" | "week" | "month" | "year";
73
+ type FeatureKind = "boolean" | "metered";
74
+ type MeterAggregation = "count" | "sum" | "max" | "latest" | "count_unique";
75
+ type MeterRoundingMode = "none" | "round" | "ceil" | "floor";
76
+ interface MeterFilter {
77
+ key: string;
78
+ values: string[];
79
+ }
80
+ /** A meter's measurement config (ADR-0011) — how a metered feature's usage aggregates. */
81
+ interface MeterConfig {
82
+ aggregation: MeterAggregation;
83
+ eventProperty?: string | null;
84
+ filters?: MeterFilter[];
85
+ roundingMode?: MeterRoundingMode;
86
+ roundingScale?: number | null;
87
+ /**
88
+ * Reset window anchoring (plan 07). Omitted/`cycle` = subscription anniversary in UTC
89
+ * (default). `calendar` tiles windows on calendar months in `resetTz` and REQUIRES a
90
+ * canonical IANA `resetTz` (e.g. `"Africa/Cairo"`, `"UTC"`).
91
+ */
92
+ resetAnchor?: "cycle" | "calendar";
93
+ resetTz?: string | null;
94
+ }
95
+ /** How a usage price turns billable quantity into money. */
96
+ type UsageModel = "graduated" | "volume" | "package" | "percentage" | "stairstep";
97
+ /** A usage-pricing tier (`upTo: null` = the unbounded last tier). */
98
+ interface UsageTier {
99
+ upTo: number | null;
100
+ unitAmount: number;
101
+ flatAmount?: number;
102
+ }
103
+
104
+ /** Public SDK types for this domain. Re-exported by ../types.ts. */
105
+
106
+ interface PriceInput {
107
+ /** `licensed` = per-unit recurring, billed × the subscription item's quantity (seats). */
108
+ kind: "fixed_recurring" | "licensed" | "one_time" | "usage";
109
+ interval?: BillingInterval;
110
+ intervalCount?: number;
111
+ /** Amount in minor units (per unit for `usage` prices without tiers). */
112
+ amount: number;
113
+ currency: string;
114
+ /** For `usage` prices: the metered feature's slug. */
115
+ meterFeature?: string;
116
+ /** For `usage` prices: the pricing model (default graduated). */
117
+ usageModel?: UsageModel;
118
+ /** For `usage` prices: tiers for graduated/volume/stairstep (overrides flat `amount`). */
119
+ usageTiers?: UsageTier[];
120
+ /** For `package` pricing: units per package (each priced at `amount`). */
121
+ packageSize?: number;
122
+ /** For `percentage` pricing: basis points of the metered amount. */
123
+ rateBps?: number;
124
+ /** Tax rate override in basis points; omit to inherit the org default, 0 = zero-rated. */
125
+ taxRateBps?: number | null;
126
+ /** Tax behavior override; omit to inherit the org default. */
127
+ taxBehavior?: TaxBehavior | null;
128
+ }
129
+ interface EntitlementInput {
130
+ /** The feature's slug (must already exist). */
131
+ feature: string;
132
+ /** Included units; `null`/omitted = unlimited. */
133
+ allowance?: number | null;
134
+ /** How often the balance refills; omitted = only on renewal. */
135
+ resetInterval?: BillingInterval;
136
+ rollover?: boolean;
137
+ }
138
+ interface CreateProductInput {
139
+ slug: string;
140
+ name: string;
141
+ type: "recurring" | "one_time";
142
+ isAddOn?: boolean;
143
+ /** Free-form string map that round-trips on the product + on subscription.* webhooks. */
144
+ metadata?: Record<string, string>;
145
+ prices: PriceInput[];
146
+ entitlements?: EntitlementInput[];
147
+ }
148
+ /** A price in a product edit: carry `id` to update that row in place, omit it to add. */
149
+ type UpdatePriceInput = PriceInput & {
150
+ id?: string;
151
+ };
152
+ /** The result of migrating a plan's subscribers to another plan (ADR-0015). */
153
+ interface MigrationResult {
154
+ /** How many subscriptions were moved to the target plan. */
155
+ migrated: number;
156
+ /** Subscriptions that couldn't be moved, with the reason (wrong currency, not active, …). */
157
+ skipped: Array<{
158
+ subscriptionId: string;
159
+ reason: string;
160
+ }>;
161
+ }
162
+ /**
163
+ * Edit a product (ADR-0015): rename, archive, and/or replace its prices/entitlements
164
+ * in place. `prices`/`entitlements`, when given, reconcile the latest version's rows
165
+ * (existing subscribers re-price at their next renewal).
166
+ */
167
+ interface UpdateProductInput {
168
+ name?: string;
169
+ archived?: boolean;
170
+ /** Replace the product's metadata map. */
171
+ metadata?: Record<string, string>;
172
+ prices?: UpdatePriceInput[];
173
+ entitlements?: EntitlementInput[];
174
+ }
175
+ interface Feature {
176
+ id: string;
177
+ slug: string;
178
+ name: string;
179
+ kind: FeatureKind;
180
+ /** Meter config for metered features (ADR-0011); null/absent for boolean. */
181
+ meter?: MeterConfig | null;
182
+ }
183
+ /**
184
+ * An in-place edit to a feature. `name` is always allowed; `meter` re-configures a
185
+ * metered feature's aggregation — but the server refuses a meter change once usage has
186
+ * been recorded (it would rewrite billed history). At least one field must be present.
187
+ */
188
+ interface UpdateFeatureInput {
189
+ name?: string;
190
+ meter?: MeterConfig;
191
+ }
192
+ /** A usage-monitoring rule: a `threshold` Alert (notifies) or a `cap` (blocks). */
193
+ type UsageAlertKind = "threshold" | "cap";
194
+ /** How an alert threshold figure reads: absolute usage, percent used, or percent remaining. */
195
+ type UsageAlertMetric = "absolute" | "percent_used" | "percent_remaining";
196
+ /** A configured Alert / Spend cap on a metered feature (Phase C). */
197
+ interface UsageAlert {
198
+ id: string;
199
+ kind: UsageAlertKind;
200
+ metric: UsageAlertMetric;
201
+ /** Threshold figures (units, or a percentage). One for a cap; one+ for an Alert. */
202
+ thresholds: number[];
203
+ /** Threshold Alerts: re-fire every N past the highest threshold; else null. */
204
+ recurringStep: number | null;
205
+ /** Threshold Alerts: also email the Customer (webhook always fires). */
206
+ notifyCustomer: boolean;
207
+ enabled: boolean;
208
+ }
209
+ interface CreateUsageAlertInput {
210
+ kind: UsageAlertKind;
211
+ metric: UsageAlertMetric;
212
+ thresholds: number[];
213
+ /** Threshold Alerts only: re-fire every N past the highest threshold. */
214
+ recurringStep?: number;
215
+ /** Threshold Alerts only: also email the Customer. */
216
+ notifyCustomer?: boolean;
217
+ }
218
+ interface TrackResult {
219
+ ok: true;
220
+ /** True when the idempotency key was already seen — the call was a no-op. */
221
+ deduplicated: boolean;
222
+ /** Remaining balance for the feature; `null` if unlimited. */
223
+ balance: number | null;
224
+ }
225
+ interface EntitlementView {
226
+ feature: string;
227
+ kind: FeatureKind;
228
+ allowed: boolean;
229
+ balance: number | null;
230
+ }
231
+ type ProductPrice = ProductPriceResponse;
232
+ type ProductEntitlement = ProductEntitlementResponse;
233
+ type Product = ProductResponse;
234
+ type Subscription = SubscriptionResponse;
235
+ /** A single fetched subscription (`subscriptions.get`) - {@link Subscription} plus the stable `customerExternalId` + `productSlug`. */
236
+ type SubscriptionDetail = SubscriptionDetailResponse;
237
+ /** A subscription line item — the base plan or an attached add-on (Phase D2). */
238
+ type SubscriptionItem = SubscriptionItemResponse;
239
+ interface CreateSubscriptionInput {
240
+ customerId: string;
241
+ /** The product slug to subscribe to. */
242
+ productId: string;
243
+ redirectionUrl?: string;
244
+ /** Optional coupon code to apply to the subscription. */
245
+ couponCode?: string;
246
+ /** Free-trial length in days (starts `trialing`, no charge until trial end). */
247
+ trialDays?: number;
248
+ /** For a trial, collect a card up front via tokenization (default true). */
249
+ collectCard?: boolean;
250
+ /** Units of the base price to bill (e.g. seats for a `licensed` plan); default 1. */
251
+ quantity?: number;
252
+ /** Limited term: total billing cycles before the subscription ends; omit = open-ended. */
253
+ cycles?: number;
254
+ /**
255
+ * The billing currency (Phase F, ADR-0013). Required only when the product has
256
+ * recurring prices in more than one currency; inferred when there's a single one.
257
+ */
258
+ currency?: string;
259
+ }
260
+ type CouponKind = "percent" | "fixed";
261
+ type CouponDuration = "once" | "repeating" | "forever";
262
+ interface CreateCouponInput {
263
+ code?: string;
264
+ kind: CouponKind;
265
+ /** percent: basis points (1% = 100 bps). fixed: minor units. */
266
+ value: number;
267
+ currency?: string;
268
+ duration: CouponDuration;
269
+ durationCycles?: number;
270
+ maxRedemptions?: number;
271
+ /** ISO-8601 timestamp. */
272
+ expiresAt?: string;
273
+ /** Product slugs the coupon is restricted to. */
274
+ productRestrictions?: string[];
275
+ }
276
+ interface Coupon {
277
+ id: string;
278
+ code: string | null;
279
+ kind: CouponKind;
280
+ value: number;
281
+ currency: string | null;
282
+ duration: CouponDuration;
283
+ durationCycles: number | null;
284
+ maxRedemptions: number | null;
285
+ redeemedCount: number;
286
+ expiresAt: string | null;
287
+ /** Set when an operator deactivated the coupon (it can no longer be applied). */
288
+ deactivatedAt?: string | null;
289
+ productRestrictions?: string[] | null;
290
+ }
291
+ type ActivatedSubscriptionResult = ActivatedSubscriptionResponse;
292
+ type PendingSubscriptionCheckoutResult = PendingSubscriptionCheckoutResponse;
293
+ type CreateSubscriptionResult = CreateSubscriptionResponse;
294
+ /**
295
+ * Why a succeeded charge could not be applied to the invoice it paid, so its money awaits an
296
+ * Operator refund: `duplicate_payment` - another payment had already paid the invoice;
297
+ * `invoice_closed` - the invoice was voided or written off before this payment arrived.
298
+ */
299
+ type UnappliedPaymentReason = "duplicate_payment" | "invoice_closed";
300
+ interface Charge {
301
+ id: string;
302
+ status: ChargeStatus;
303
+ customerId: string;
304
+ customerExternalId: string;
305
+ invoiceId: string | null;
306
+ kind: "customer_present" | "mit";
307
+ subtotal: number;
308
+ tax: number;
309
+ total: number;
310
+ taxRateBps: number | null;
311
+ taxBehavior: "exclusive" | "inclusive" | null;
312
+ items: {
313
+ name: string;
314
+ amount: number;
315
+ quantity: number;
316
+ description?: string;
317
+ }[];
318
+ /** Backward-compatible alias of `total`, the amount sent to the provider. */
319
+ amount: number;
320
+ currency: string;
321
+ checkoutUrl: string | null;
322
+ specialReference: string;
323
+ providerTransactionId: string | null;
324
+ errorMessage: string | null;
325
+ /** Provider-reported dispute/chargeback (PRD-08), read-only. Null unless disputed. */
326
+ disputedAt: string | null;
327
+ disputeReason: string | null;
328
+ disputeId: string | null;
329
+ disputeAmount: number | null;
330
+ disputeState: "open" | "won" | "lost" | null;
331
+ /** Set when this payment could not be applied to its invoice; null otherwise. */
332
+ unappliedReason: UnappliedPaymentReason | null;
333
+ createdAt: string;
334
+ }
335
+ interface CreateChargeInput {
336
+ customerId: string;
337
+ /** Amount in minor units (e.g. piasters). */
338
+ amount: number;
339
+ currency: string;
340
+ /** Tax rate in basis points. When set, `taxBehavior` defaults to `exclusive`. */
341
+ taxRateBps?: number;
342
+ /** Whether `amount` excludes or includes tax. Requires `taxRateBps`. */
343
+ taxBehavior?: "exclusive" | "inclusive";
344
+ items?: {
345
+ name: string;
346
+ amount: number;
347
+ quantity: number;
348
+ description?: string;
349
+ }[];
350
+ /** Restrict the offered methods; omit to offer every method the merchant has configured. */
351
+ methods?: ("card" | "wallet" | "sympl" | "applepay")[];
352
+ saveCard?: boolean;
353
+ redirectionUrl?: string;
354
+ }
355
+ /** A standalone off-session charge against the customer's current saved card. */
356
+ interface CreateMerchantInitiatedChargeInput {
357
+ customerId: string;
358
+ paymentMethodId: string;
359
+ amount: number;
360
+ currency: string;
361
+ items: {
362
+ name: string;
363
+ amount: number;
364
+ quantity: number;
365
+ description?: string;
366
+ }[];
367
+ taxRateBps?: number;
368
+ taxBehavior?: "exclusive" | "inclusive";
369
+ }
370
+
371
+ export type { AttachResult as A, BillowOptions as B, CreateProductInput as C, PriceInput as D, EntitlementView as E, FeatureKind as F, ProductEntitlement as G, ProductPrice as H, IdempotentRequestOptions as I, SubscriptionDetail as J, SubscriptionItem as K, UpdatePriceInput as L, MeterConfig as M, UsageAlertKind as N, UsageAlertMetric as O, Product as P, UsageModel as Q, RequestOptions as R, Subscription as S, TrackResult as T, UnappliedPaymentReason as U, UsageTier as V, CreateCouponInput as a, CheckResult as b, TaxBehavior as c, Customer as d, CreateChargeInput as e, Charge as f, CreateMerchantInitiatedChargeInput as g, CallOptions as h, UpdateProductInput as i, MigrationResult as j, CreateSubscriptionInput as k, Coupon as l, Feature as m, UpdateFeatureInput as n, UsageAlert as o, CreateUsageAlertInput as p, ActivatedSubscriptionResult as q, BillingInterval as r, CouponDuration as s, CouponKind as t, CreateSubscriptionResult as u, EntitlementInput as v, MeterAggregation as w, MeterFilter as x, MeterRoundingMode as y, PendingSubscriptionCheckoutResult as z };
@@ -117,10 +117,10 @@ function resolveBaseUrl(explicit) {
117
117
  const fromEnv = typeof process !== "undefined" ? process.env?.["BILLOW_URL"] : void 0;
118
118
  return (explicit || fromEnv || DEFAULT_BASE_URL).replace(/\/$/, "");
119
119
  }
120
- async function apiRequest(ctx, method, path, body, options) {
121
- return (await apiRequestWithStatus(ctx, method, path, body, options)).data;
120
+ async function apiRequest(ctx, method, path2, body, options) {
121
+ return (await apiRequestWithStatus(ctx, method, path2, body, options)).data;
122
122
  }
123
- async function apiRequestWithStatus(ctx, method, path, body, options) {
123
+ async function apiRequestWithStatus(ctx, method, path2, body, options) {
124
124
  const init = {
125
125
  method,
126
126
  headers: {
@@ -130,7 +130,7 @@ async function apiRequestWithStatus(ctx, method, path, body, options) {
130
130
  }
131
131
  };
132
132
  if (body) init.body = JSON.stringify(body);
133
- const res = await sendWithResilience(ctx, `${ctx.baseUrl}${path}`, init, {
133
+ const res = await sendWithResilience(ctx, `${ctx.baseUrl}${path2}`, init, {
134
134
  timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,
135
135
  safeToRepeat: safeToRepeat(method, !!options?.idempotencyKey),
136
136
  callerSignal: options?.signal
@@ -176,10 +176,10 @@ function errorFrom(res, data) {
176
176
  requestIdOf(res)
177
177
  );
178
178
  }
179
- async function apiRequestBinary(ctx, method, path, options) {
179
+ async function apiRequestBinary(ctx, method, path2, options) {
180
180
  const res = await sendWithResilience(
181
181
  ctx,
182
- `${ctx.baseUrl}${path}`,
182
+ `${ctx.baseUrl}${path2}`,
183
183
  { method, headers: { authorization: `Bearer ${ctx.bearer}` } },
184
184
  {
185
185
  timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,
@@ -487,11 +487,14 @@ function createCreditsResource(ctx) {
487
487
  * checkout to send the buyer to. Credits are granted when the payment succeeds (listen for
488
488
  * `credit_top_up.succeeded`). The `idempotencyKey` is required and must identify this one
489
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.
490
+ * buys nothing again - asking the payment provider again for a checkout the first attempt
491
+ * could not open, within its page's 40 minutes (Stripe only in the first 10, while 30
492
+ * remain: it answers a timed-out first attempt with the checkout it made, or makes one never
493
+ * made; Paymob only if the first attempt never made one); reusing it for another purchase
494
+ * throws `idempotency_conflict` (409). A retry can also throw `conflict` (409), with
495
+ * `details.reason`: `checkout_in_progress` - another request is still opening the
496
+ * checkout, so retry the same key shortly - or `checkout_unavailable` - its checkout could
497
+ * not be opened and never will be, so buy again under a new key.
495
498
  */
496
499
  create: async (input, options) => {
497
500
  const { status, data } = await apiRequestWithStatus(
@@ -573,6 +576,155 @@ function createCreditsResource(ctx) {
573
576
  void 0,
574
577
  options
575
578
  )
579
+ },
580
+ rateCards: {
581
+ /**
582
+ * Publish a new Rate Card version: what each Credit Action costs, as
583
+ * `ceil(units * unitPrice / perUnits)` microcredits a line. Versions are immutable - a price
584
+ * change is a new version - and take effect in order, at `effectiveAt` (now when omitted).
585
+ */
586
+ publish: (input, options) => apiRequest(ctx, "POST", "/v1/credits/rate-cards", input, options),
587
+ /** The version in effect now (`not_found` before the first takes effect). */
588
+ current: (options) => apiRequest(
589
+ ctx,
590
+ "GET",
591
+ "/v1/credits/rate-cards/current",
592
+ void 0,
593
+ options
594
+ ),
595
+ /** One version, by its number. */
596
+ get: (version, options) => apiRequest(
597
+ ctx,
598
+ "GET",
599
+ `/v1/credits/rate-cards/${encodeURIComponent(String(version))}`,
600
+ void 0,
601
+ options
602
+ ),
603
+ /**
604
+ * The published versions, newest first. Auto-paginating by cursor: `await` the first page,
605
+ * `for await (…)` every version, or `.listAll()` to collect them.
606
+ */
607
+ list: (params = {}, options) => makeCursorListPromise(
608
+ (p) => apiRequest(
609
+ ctx,
610
+ "GET",
611
+ `/v1/credits/rate-cards${toQuery(p)}`,
612
+ void 0,
613
+ options
614
+ ),
615
+ params
616
+ )
617
+ },
618
+ quotes: {
619
+ /**
620
+ * Price a customer's Credit Actions (all of one category) at the current Rate Card version
621
+ * and keep the price for 15 minutes: a reservation over the quote holds exactly its
622
+ * `amount`, and its commit prices at the version the quote pinned. A quote holds nothing.
623
+ */
624
+ create: (input, options) => apiRequest(ctx, "POST", "/v1/credits/quotes", input, options),
625
+ /** A quote as it was made. */
626
+ get: (quoteId, options) => apiRequest(
627
+ ctx,
628
+ "GET",
629
+ `/v1/credits/quotes/${encodeURIComponent(quoteId)}`,
630
+ void 0,
631
+ options
632
+ )
633
+ },
634
+ reservations: {
635
+ /**
636
+ * Hold credits for one operation: over a `quoteId`, or over `items` priced at the current
637
+ * Rate Card version. The `idempotencyKey` is required: a retry under it - after a lost
638
+ * reply, even once the quote expired - answers the same reservation (`replayed: true`) and
639
+ * holds nothing again. Reusing it for another request, or a new key with an `operationKey`
640
+ * the customer already used (`details.reason: "operation_key_used"`), throws
641
+ * `idempotency_conflict` (409). Throws `insufficient_credits` (402), `account_frozen`
642
+ * (423), `account_closed` (409), `conflict` (409, `details.reason: "quote_expired"`), or
643
+ * `validation_error` (422) - e.g. `details.reason: "zero_amount"` when the items cost nothing.
644
+ * The `operationKey` is an operation id, never personal data: erasure replaces it.
645
+ */
646
+ create: async (input, options) => {
647
+ const { status, data } = await apiRequestWithStatus(
648
+ ctx,
649
+ "POST",
650
+ "/v1/credits/reservations",
651
+ input,
652
+ options
653
+ );
654
+ return { ...data, replayed: status === 200 };
655
+ },
656
+ /** A reservation as it now stands. */
657
+ get: (reservationId, options) => apiRequest(
658
+ ctx,
659
+ "GET",
660
+ `/v1/credits/reservations/${encodeURIComponent(reservationId)}`,
661
+ void 0,
662
+ options
663
+ ),
664
+ /**
665
+ * A customer's reservation (by external id) made with `operationKey` - how to find one whose
666
+ * reply was lost. Throws `not_found` (404) when there is none.
667
+ */
668
+ findByOperationKey: (customer, operationKey, options) => apiRequest(
669
+ ctx,
670
+ "GET",
671
+ `/v1/credits/reservations${toQuery({ customer, operationKey })}`,
672
+ void 0,
673
+ options
674
+ ),
675
+ /**
676
+ * Commit what the operation used: lines priced at the reservation's Rate Card version,
677
+ * consuming at most what it holds (`commit_exceeds_hold`, 409) and releasing the rest; lines
678
+ * that cost nothing in all consume nothing (`consumed: "0"`, `consumptionId: null`). The
679
+ * same commit again answers it again, so it is safe to retry; anything else after it throws
680
+ * `invalid_state_transition` (409) with the reservation in `details` - with
681
+ * `details.reason: "reservation_expired"` once the hold is past its `expiresAt`.
682
+ */
683
+ commit: (reservationId, items, options) => apiRequest(
684
+ ctx,
685
+ "POST",
686
+ `/v1/credits/reservations/${encodeURIComponent(reservationId)}/commit`,
687
+ { items },
688
+ options
689
+ ),
690
+ /** Release the whole hold. Safe to retry; after a commit it throws (409). */
691
+ release: (reservationId, options) => apiRequest(
692
+ ctx,
693
+ "POST",
694
+ `/v1/credits/reservations/${encodeURIComponent(reservationId)}/release`,
695
+ void 0,
696
+ options
697
+ ),
698
+ /**
699
+ * Protect a held reservation whose operation was paid for and is still being fulfilled: it
700
+ * never expires (commit or release it when done). Safe to retry. A hold already past its
701
+ * `expiresAt` throws `invalid_state_transition` (409, `details.reason: "reservation_expired"`).
702
+ */
703
+ protect: (reservationId, options) => apiRequest(
704
+ ctx,
705
+ "POST",
706
+ `/v1/credits/reservations/${encodeURIComponent(reservationId)}/protect`,
707
+ void 0,
708
+ options
709
+ )
710
+ },
711
+ consumptions: {
712
+ /**
713
+ * Give back part or all of a commit's consumption (`consumptionId` on the committed
714
+ * reservation), never more than is left (`reversal_exceeds_consumption`, 409). The
715
+ * `idempotencyKey` is required: a retry under it answers the same reversal
716
+ * (`replayed: true`) and gives nothing back again.
717
+ */
718
+ reverse: async (consumptionId, input, options) => {
719
+ const { status, data } = await apiRequestWithStatus(
720
+ ctx,
721
+ "POST",
722
+ `/v1/credits/consumptions/${encodeURIComponent(consumptionId)}/reverse`,
723
+ input,
724
+ options
725
+ );
726
+ return { ...data, replayed: status === 200 };
727
+ }
576
728
  }
577
729
  };
578
730
  }
@@ -685,6 +837,33 @@ function createDeliverabilityResource(ctx) {
685
837
  };
686
838
  }
687
839
 
840
+ // src/resources/hosted-domains.ts
841
+ var path = (id, action = "") => `/v1/hosted-domains/${encodeURIComponent(id)}${action}`;
842
+ function createHostedDomainsResource(ctx) {
843
+ return {
844
+ /** The Project's domains, and whether its plan allows adding more (`whiteLabel`). */
845
+ list: (options) => apiRequest(ctx, "GET", "/v1/hosted-domains", void 0, options),
846
+ /**
847
+ * Add a subdomain such as `billing.example.com`. Answers the domain with the TXT and CNAME
848
+ * `records` to create. Needs a plan with white-label branding (`permission_denied` otherwise).
849
+ */
850
+ create: (input, options) => apiRequest(ctx, "POST", "/v1/hosted-domains", input, options),
851
+ get: (id, options) => apiRequest(ctx, "GET", path(id), void 0, options),
852
+ /** Check the DNS records now (2 per minute per domain); answers the refreshed domain. */
853
+ verify: (id, options) => apiRequest(ctx, "POST", path(id, "/verify"), void 0, options),
854
+ /** Make an active domain the default host for new portal sessions. */
855
+ makePrimary: (id, options) => apiRequest(ctx, "POST", path(id, "/primary"), void 0, options),
856
+ /** Remove a domain; the portal sessions on it end immediately. */
857
+ remove: (id, options) => apiRequest(
858
+ ctx,
859
+ "DELETE",
860
+ path(id),
861
+ void 0,
862
+ options
863
+ )
864
+ };
865
+ }
866
+
688
867
  // src/resources/invoices.ts
689
868
  function createInvoicesResource(ctx) {
690
869
  return {
@@ -1150,6 +1329,8 @@ var Billow = class {
1150
1329
  portalSessions;
1151
1330
  deliverability;
1152
1331
  marketplace;
1332
+ /** Custom domains for the hosted customer portal (live keys, behind a rollout flag). */
1333
+ hostedDomains;
1153
1334
  /**
1154
1335
  * Organization settings (Phase D3, F) — the configurable dunning schedule, usage settlement grace,
1155
1336
  * and the cross-currency reporting currency + FX-rate registry (ADR-0013).
@@ -1170,10 +1351,11 @@ var Billow = class {
1170
1351
  this.portalSessions = createPortalSessionsResource(this.#ctx);
1171
1352
  this.deliverability = createDeliverabilityResource(this.#ctx);
1172
1353
  this.marketplace = createMarketplaceResource(this.#ctx);
1354
+ this.hostedDomains = createHostedDomainsResource(this.#ctx);
1173
1355
  this.settings = createSettingsResource(this.#ctx);
1174
1356
  }
1175
- #request(method, path, body, options) {
1176
- return apiRequest(this.#ctx, method, path, body, options);
1357
+ #request(method, path2, body, options) {
1358
+ return apiRequest(this.#ctx, method, path2, body, options);
1177
1359
  }
1178
1360
  coupons = {
1179
1361
  /** Define a coupon (a reusable discount template). */
@@ -1489,5 +1671,5 @@ var BillowPortal = class {
1489
1671
  };
1490
1672
 
1491
1673
  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
1674
+ //# sourceMappingURL=chunk-DYWGZRE2.js.map
1675
+ //# sourceMappingURL=chunk-DYWGZRE2.js.map