@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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @usebillow/sdk
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - a7dff2a: Prepaid credits can be spent. `credits.rateCards.publish`, `current`, `get(version)` and `list` manage immutable Rate Card versions that price each Credit Action as `ceil(units * unitPrice / perUnits)` microcredits. `credits.quotes.create` prices a customer's actions at the current version and pins it for 15 minutes; `credits.quotes.get` reads one back. `credits.reservations.create(input, { idempotencyKey })` holds credits over a quote or over items (answering `replayed` for a retry), `get` and `findByOperationKey(customer, operationKey)` read one, and `commit(id, items)`, `release(id)` and `protect(id)` settle it, the commit priced at the reservation's pinned version. `credits.consumptions.reverse(consumptionId, input, { idempotencyKey })` gives back part or all of a commit. Counts of units travel as decimal strings like credit quantities, and the customer data export types carry `quotes` and each reservation's `quoteId` and `rateCardVersion`.
8
+ - d80455e: Custom domains for the hosted customer portal. `hostedDomains.create({ hostname })` adds a subdomain and answers the TXT and CNAME `records` to create; `hostedDomains.list()`, `get`, `verify` (check the DNS now), `makePrimary` and `remove` (which answers the removed hostname) manage them. They need a live secret key and a plan with white-label branding (a `permission_denied` refusal names the plan in its `details`). `me()` reports `hostedDomainsEnabled`. Webhook payload types cover the new `hosted_domain.activated`, `hosted_domain.dns_failing`, `hosted_domain.reassignment_pending` and `hosted_domain.disabled` events, grouped as `HOSTED_DOMAIN_EVENT_TYPES`.
9
+
10
+ ### Patch Changes
11
+
12
+ - 4fe3a05: Document when retrying `credits.topUps.create` under the same `idempotencyKey` opens a checkout the first attempt could not: within the page's 40 minutes, Stripe only in the first 10 (while 30 remain), answering a timed-out first attempt with the checkout it made or making one never made, and Paymob only if the first attempt never made one. Otherwise the retry answers `checkout_unavailable`.
13
+
3
14
  ## 0.5.0
4
15
 
5
16
  ### Minor Changes
package/README.md CHANGED
@@ -88,6 +88,15 @@ for await (const entry of billow.credits.ledger.list(customerId)) {
88
88
  const usage = await billow.credits.usage.get(customerId, { groupBy: "category" });
89
89
  ```
90
90
 
91
+ Serve the hosted customer portal on your own subdomain (live secret key, a plan with white-label
92
+ branding): add the hostname, create the two DNS records it answers, and Billow verifies them.
93
+
94
+ ```ts
95
+ const domain = await billow.hostedDomains.create({ hostname: "billing.example.com" });
96
+ // domain.records: the _billow-challenge TXT record and the CNAME to create
97
+ const checked = await billow.hostedDomains.verify(domain.id); // check now; read checked.status
98
+ ```
99
+
91
100
  ## Auto-metering (LLM tokens & any usage source)
92
101
 
93
102
  Stop hand-instrumenting usage. `@usebillow/sdk/ingestion` wraps your calls and reports
@@ -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.cjs';
2
+ import { C as ChargeStatus } from './billing-status-BZQN_gm7.cjs';
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 };