@usebillow/sdk 0.11.0 → 0.13.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/index.ts","../src/react.tsx"],"names":["safeToRepeat","createContext","useCallback","useState","useEffect","useMemo","jsx","useContext"],"mappings":"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","file":"react.cjs","sourcesContent":["import type { BillowErrorCode } from \"./error-codes\";\nimport type { BillowOptions, CallOptions, RequestOptions } from \"./types\";\n\n/** The response header carrying billow's per-request id (echoed on success and error alike). */\nconst REQUEST_ID_HEADER = \"x-request-id\";\n\n/** Build a `?a=b&c=d` string, skipping undefined/null/empty values. */\nexport function toQuery(params: object): string {\n const q = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null && v !== \"\") q.set(k, String(v));\n }\n const s = q.toString();\n return s ? `?${s}` : \"\";\n}\n\nexport class BillowApiError extends Error {\n readonly status: number;\n /**\n * A known {@link BillowErrorCode} (widened to `string` so a newer server's code still type-checks),\n * or one the SDK raises itself: `invalid_response` for a success (2xx other than 204) whose body\n * was empty or could not be read or parsed. The request reached billow and was answered, but what\n * it answered is unknown - for a write, treat the outcome as UNKNOWN (retry it under the same\n * idempotency key), never as done or as refused.\n */\n readonly code: BillowErrorCode | (string & {});\n /** Structured error context from the server (e.g. per-field validation issues), when present. */\n readonly details?: unknown;\n /** billow's per-request id (`x-request-id`) - quote it in a bug report to trace the server log. */\n readonly requestId?: string | undefined;\n /**\n * How many seconds to wait before trying again, from the response's `Retry-After` header (sent\n * with a `429 rate_limited`); undefined when the response did not say.\n */\n readonly retryAfterSeconds?: number | undefined;\n\n constructor(\n status: number,\n code: BillowErrorCode | (string & {}),\n message: string,\n details?: unknown,\n requestId?: string,\n retryAfterSeconds?: number,\n ) {\n super(message);\n this.name = \"BillowApiError\";\n this.status = status;\n this.code = code;\n this.details = details;\n this.requestId = requestId;\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\n/** Hard per-request timeout when a caller doesn't set one. */\nconst DEFAULT_TIMEOUT_MS = 60_000;\n/** Base retry backoff when a caller doesn't set one. */\nconst DEFAULT_RETRY_BACKOFF_MS = 500;\n/** The longest wait a `Retry-After` is taken at: anything longer reads as an hour. */\nconst MAX_RETRY_AFTER_SECONDS = 3600;\n\n/** Everything a request needs: the transport, the base URL, the bearer, and resilience knobs. */\nexport interface RequestContext {\n fetch: typeof fetch;\n baseUrl: string;\n /** The bearer - a secret/publishable API key, or a portal-session token. */\n bearer: string;\n timeoutMs: number;\n maxRetries: number;\n retryBackoffMs: number;\n}\n\nexport function makeContext(bearer: string, opts: BillowOptions): RequestContext {\n return {\n fetch: opts.fetch ?? fetch,\n baseUrl: resolveBaseUrl(opts.baseUrl),\n bearer,\n timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n maxRetries: opts.maxRetries ?? 0,\n retryBackoffMs: opts.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS,\n };\n}\n\n/**\n * Whether a failed attempt is safe to retry. A `null` status is a network error or timeout\n * (no response). A `429` never executed (rate-limited), so it is safe to repeat regardless; a\n * network error / `5xx` might have applied a write, so those retry only when the request is\n * `safeToRepeat` - an idempotent method (`GET`/`HEAD`) or an idempotency-keyed write.\n */\nfunction isRetryable(status: number | null, safeToRepeat: boolean): boolean {\n if (status === 429) return true;\n if (status === null) return safeToRepeat;\n return safeToRepeat && status >= 500 && status <= 599;\n}\n\n/**\n * Backoff before the next attempt: honor `Retry-After` (seconds, at most an hour) if given, else\n * exponential.\n */\nfunction backoffMs(base: number, attempt: number, retryAfter: string | null): number {\n if (retryAfter) {\n const secs = Number(retryAfter);\n if (Number.isFinite(secs) && secs >= 0) return Math.min(secs, MAX_RETRY_AFTER_SECONDS) * 1000;\n }\n return base * 2 ** attempt;\n}\n\n/** The reason an aborted signal carries, or a plain `AbortError` when none was given. */\nfunction abortReason(signal: AbortSignal): unknown {\n return signal.reason ?? new DOMException(\"The operation was aborted.\", \"AbortError\");\n}\n\n/** A backoff sleep that a caller's abort can cut short. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(abortReason(signal));\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(abortReason(signal));\n },\n { once: true },\n );\n });\n}\n\ninterface SendOptions {\n timeoutMs: number;\n /** Retry a network error / `5xx` (a `429` always retries) - set for GET/HEAD or keyed writes. */\n safeToRepeat: boolean;\n /** The caller's cancellation signal, if any. A caller abort is surfaced at once, never retried. */\n callerSignal: AbortSignal | undefined;\n}\n\n/**\n * Send one request with a per-attempt timeout and opt-in retries. Returns the final `Response`\n * (which may carry a non-retryable error status for the caller to parse); throws on a network\n * error / timeout that leaves no response, or immediately with the caller's reason if the\n * caller's `signal` aborts. Retryable responses are discarded and re-issued up to `maxRetries`\n * with {@link backoffMs} between attempts. The timeout is applied **per attempt**.\n */\nasync function sendWithResilience(\n ctx: RequestContext,\n url: string,\n init: RequestInit,\n opts: SendOptions,\n): Promise<Response> {\n const { timeoutMs, safeToRepeat, callerSignal } = opts;\n if (callerSignal?.aborted) throw abortReason(callerSignal);\n\n for (let attempt = 0; ; attempt++) {\n // A fresh controller per attempt, aborted by either the per-attempt timeout or the caller.\n const controller = new AbortController();\n let timedOut = false;\n const onCallerAbort = () => controller.abort(abortReason(callerSignal as AbortSignal));\n callerSignal?.addEventListener(\"abort\", onCallerAbort, { once: true });\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs)\n : undefined;\n\n let res: Response | null = null;\n let err: unknown;\n try {\n res = await ctx.fetch(url, { ...init, signal: controller.signal });\n } catch (e) {\n err = e;\n } finally {\n if (timer) clearTimeout(timer);\n callerSignal?.removeEventListener(\"abort\", onCallerAbort);\n }\n\n // The caller cancelled - surface their reason immediately, never retry.\n if (callerSignal?.aborted) throw abortReason(callerSignal);\n\n if (res?.ok) return res;\n const status = res ? res.status : null;\n if (attempt < ctx.maxRetries && isRetryable(status, safeToRepeat)) {\n await sleep(\n backoffMs(ctx.retryBackoffMs, attempt, res?.headers.get(\"retry-after\") ?? null),\n callerSignal,\n );\n continue;\n }\n if (res) return res; // non-retryable (or retries exhausted) HTTP error - caller parses the body\n if (timedOut) throw new Error(`billow request timed out after ${timeoutMs}ms`);\n throw err instanceof Error ? err : new Error(\"billow request failed\");\n }\n}\n\n/** A method with no request body is inherently safe to repeat; a write needs an idempotency key. */\nfunction safeToRepeat(method: string, hasIdempotencyKey: boolean): boolean {\n return method === \"GET\" || method === \"HEAD\" || hasIdempotencyKey;\n}\n\n/** The hosted Billow API, which a client talks to unless told otherwise. */\nexport const DEFAULT_BASE_URL = \"https://api.usebillow.com\";\n\n/**\n * Resolve the API base URL: the explicit `baseUrl` option first, then the `BILLOW_URL`\n * environment variable (Node), then the hosted API. Local development and self-hosted\n * servers set one of the first two. Trailing slash stripped.\n */\nfunction resolveBaseUrl(explicit?: string): string {\n // biome-ignore lint/complexity/useLiteralKeys: the SDK cannot augment every consumer's ProcessEnv.\n const fromEnv = typeof process !== \"undefined\" ? process.env?.[\"BILLOW_URL\"] : undefined;\n return (explicit || fromEnv || DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n}\n\nexport async function apiRequest<T>(\n ctx: RequestContext,\n method: string,\n path: string,\n body?: unknown,\n options?: RequestOptions,\n): Promise<T> {\n return (await apiRequestWithStatus<T>(ctx, method, path, body, options)).data;\n}\n\n/**\n * {@link apiRequest}, also answering the success status - for the writes whose status carries\n * meaning (an idempotent create answers `201` when it created, `200` when it replayed).\n */\nexport async function apiRequestWithStatus<T>(\n ctx: RequestContext,\n method: string,\n path: string,\n body?: unknown,\n options?: RequestOptions,\n): Promise<{ status: number; data: T }> {\n const init: RequestInit = {\n method,\n headers: {\n authorization: `Bearer ${ctx.bearer}`,\n ...(body ? { \"content-type\": \"application/json\" } : {}),\n ...(options?.idempotencyKey ? { \"idempotency-key\": options.idempotencyKey } : {}),\n },\n };\n if (body) init.body = JSON.stringify(body);\n const res = await sendWithResilience(ctx, `${ctx.baseUrl}${path}`, init, {\n timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,\n safeToRepeat: safeToRepeat(method, !!options?.idempotencyKey),\n callerSignal: options?.signal,\n });\n // null: the body could not be read (the connection dropped mid-body, say).\n const text = await res.text().catch(() => null);\n if (!res.ok) throw errorFrom(res, safeParse(text ?? \"\"));\n return { status: res.status, data: parseSuccessBody<T>(res, text) };\n}\n\n/** billow's per-request id from a response, for {@link BillowApiError.requestId}. */\nfunction requestIdOf(res: Response): string | undefined {\n return res.headers.get(REQUEST_ID_HEADER) ?? undefined;\n}\n\n/** Parse leniently - a malformed *error* body still yields the HTTP-status error, not a parse error. */\nfunction safeParse(text: string): unknown {\n if (text.trim() === \"\") return {};\n try {\n return JSON.parse(text);\n } catch {\n return {};\n }\n}\n\n/**\n * A successful response must carry valid JSON; only a `204 No Content` (a delete) carries none. An\n * empty, unreadable or malformed 2xx body is an `invalid_response` error, never a silently-empty\n * `{}` - which a caller would take for an answer (a write it thinks done, with `undefined` fields).\n */\nfunction parseSuccessBody<T>(res: Response, text: string | null): T {\n if (res.status === 204) return {} as T;\n if (text !== null && text.trim() !== \"\") {\n try {\n return JSON.parse(text) as T;\n } catch {\n // Falls through to the error below.\n }\n }\n throw new BillowApiError(\n res.status,\n \"invalid_response\",\n `billow returned an empty or unreadable response body (HTTP ${res.status})`,\n undefined,\n requestIdOf(res),\n );\n}\n\n/** Build a {@link BillowApiError} from a failed response (surfacing `details` + the `requestId`). */\nfunction errorFrom(res: Response, data: unknown): BillowApiError {\n const err =\n (data as { error?: { code?: string; message?: string; details?: unknown } }).error ?? {};\n return new BillowApiError(\n res.status,\n err.code ?? \"error\",\n err.message ?? String(res.status),\n err.details,\n requestIdOf(res),\n retryAfterOf(res),\n );\n}\n\n/** An HTTP date in its preferred form (IMF-fixdate, RFC 9110): `Sun, 06 Nov 1994 08:49:37 GMT`. */\nconst IMF_FIXDATE = /^[A-Z][a-z]{2}, \\d{2} [A-Z][a-z]{2} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT$/;\n\n/**\n * The seconds a response's `Retry-After` asks a client to wait: whole seconds, or an HTTP date\n * counted from now (never below 0), at most an hour. Undefined when absent or unreadable: a value\n * that is neither (a fraction, a sign, a date in another form) is not guessed at.\n */\nfunction retryAfterOf(res: Response): number | undefined {\n const value = res.headers.get(\"retry-after\")?.trim();\n if (!value) return undefined;\n let seconds: number;\n if (/^\\d+$/.test(value)) {\n seconds = Number(value);\n } else if (IMF_FIXDATE.test(value)) {\n seconds = Math.ceil((Date.parse(value) - Date.now()) / 1000);\n } else {\n return undefined;\n }\n if (!Number.isFinite(seconds)) return undefined;\n return Math.min(Math.max(0, seconds), MAX_RETRY_AFTER_SECONDS);\n}\n\n/** Fetch a binary endpoint (e.g. a PDF) as raw bytes (a valid `BodyInit`). */\nexport async function apiRequestBinary(\n ctx: RequestContext,\n method: string,\n path: string,\n options?: CallOptions,\n): Promise<ArrayBuffer> {\n const res = await sendWithResilience(\n ctx,\n `${ctx.baseUrl}${path}`,\n { method, headers: { authorization: `Bearer ${ctx.bearer}` } },\n {\n timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,\n safeToRepeat: safeToRepeat(method, false),\n callerSignal: options?.signal,\n },\n );\n if (!res.ok) throw errorFrom(res, safeParse(await res.text().catch(() => \"\")));\n return res.arrayBuffer();\n}\n","/**\n * billow client SDK - the \"two lines of code\" surface, mirroring Autumn's shape\n * (CONTEXT/ARCHITECTURE §10). A thin typed wrapper over the billow REST API.\n *\n * const billow = new Billow(process.env.BILLOW_SECRET_KEY!);\n * const { allowed } = await billow.check({ customerId, featureId: \"projects\" });\n *\n * Public request/response types live in ./types and are re-exported here, so the\n * package root stays the single import surface. They are contract-derived and inlined\n * into the shipped .d.ts at build time (tsup `dts.resolve` reads @billow/contracts'\n * bundled dist), so the declarations carry no bare `@billow/contracts` import a\n * consumer couldn't resolve.\n *\n * We deliberately do NOT re-export @billow/contracts' runtime zod schemas: a client\n * SDK shouldn't ship validation - the server is the schema authority - and\n * re-exporting them made @billow/contracts (a private, unpublished workspace package)\n * a runtime dependency that left the published SDK impossible to install. Contracts is\n * now a devDependency. The types are zod-inferred, so the declarations do reference\n * `zod` (a small, published package kept as a runtime dependency); the runtime bundle\n * itself imports neither zod nor @billow/contracts.\n */\n\nexport type {\n AddSubscriptionItemInput as ContractAddSubscriptionItemInput,\n ApplySubscriptionCouponInput as ContractApplySubscriptionCouponInput,\n CancelSubscriptionInput as ContractCancelSubscriptionInput,\n ChangeSubscriptionPlanInput as ContractChangeSubscriptionPlanInput,\n CreateChargeInput as ContractCreateChargeInput,\n CreateCustomerInput as ContractCreateCustomerInput,\n CreateMerchantInitiatedChargeInput as ContractCreateMerchantInitiatedChargeInput,\n CreatePortalSessionInput as ContractCreatePortalSessionInput,\n CreateProductInput as ContractCreateProductInput,\n CreateSubscriptionInput as ContractCreateSubscriptionInput,\n CreateWebhookEndpointInput as ContractCreateWebhookEndpointInput,\n MigrateSubscribersInput as ContractMigrateSubscribersInput,\n RefundChargeInput as ContractRefundChargeInput,\n ResolveRefundInput as ContractResolveRefundInput,\n UpdateProductInput as ContractUpdateProductInput,\n UpdateSubscriptionItemInput as ContractUpdateSubscriptionItemInput,\n UpdateWebhookEndpointInput as ContractUpdateWebhookEndpointInput,\n} from \"@billow/contracts\";\nexport * from \"./billing-status\";\nexport { BillowApiError } from \"./client\";\nexport * from \"./error-codes\";\nexport * from \"./money\";\nexport * from \"./subscription-status\";\nexport * from \"./types\";\n\nimport { apiRequest, apiRequestBinary, makeContext, type RequestContext, toQuery } from \"./client\";\nimport { makeListPromise } from \"./pagination\";\nimport { createChargesResource, createCheckoutMethodsResource } from \"./resources/charges\";\nimport { createCreditsResource } from \"./resources/credits\";\nimport { createCustomersResource } from \"./resources/customers\";\nimport { createDeliverabilityResource } from \"./resources/deliverability\";\nimport { createHostedDomainsResource } from \"./resources/hosted-domains\";\nimport { createInvoicesResource } from \"./resources/invoices\";\nimport { createMarketplaceResource } from \"./resources/marketplace\";\nimport { createMetricsResource } from \"./resources/metrics\";\nimport { createPortalSessionsResource } from \"./resources/portal-sessions\";\nimport { createProductsResource } from \"./resources/products\";\nimport { createSettingsResource } from \"./resources/settings\";\nimport { createSubscriptionsResource } from \"./resources/subscriptions\";\nimport { createWebhookEndpointsResource } from \"./resources/webhook-endpoints\";\nimport type {\n AttachResult,\n BillowOptions,\n BusinessProfile,\n CallOptions,\n CheckResult,\n Coupon,\n CreateCouponInput,\n CreateSubscriptionInput,\n CreateUsageAlertInput,\n CredentialSet,\n EntitlementView,\n Feature,\n FeatureKind,\n InvoiceListItem,\n InvoicePaymentResult,\n ListParams,\n ListPromise,\n MeterConfig,\n Paginated,\n PayInvoiceInput,\n PaymentProviderDefinition,\n PortalCardSetupResult,\n PortalCheckout,\n PortalPlanChange,\n PortalSessionExchange,\n PortalSessionExchangeInput,\n PortalSessionInfo,\n PortalSubscriptionStatus,\n PortalView,\n Product,\n ProviderCredentialSummary,\n ProviderVerifyResult,\n RequestOptions,\n SavedPaymentMethod,\n SearchResults,\n TrackResult,\n UpdateBusinessProfileInput,\n UpdateFeatureInput,\n UpsertCredentialsInput,\n UsageAlert,\n Whoami,\n} from \"./types\";\n\nexport class Billow {\n #ctx: RequestContext;\n\n customers!: ReturnType<typeof createCustomersResource>;\n charges!: ReturnType<typeof createChargesResource>;\n checkoutMethods!: ReturnType<typeof createCheckoutMethodsResource>;\n /** Prepaid credits: grant them to a Customer (quantities are decimal strings of microcredits). */\n credits!: ReturnType<typeof createCreditsResource>;\n invoices!: ReturnType<typeof createInvoicesResource>;\n metrics!: ReturnType<typeof createMetricsResource>;\n webhookEndpoints!: ReturnType<typeof createWebhookEndpointsResource>;\n products!: ReturnType<typeof createProductsResource>;\n subscriptions!: ReturnType<typeof createSubscriptionsResource>;\n portalSessions!: ReturnType<typeof createPortalSessionsResource>;\n deliverability!: ReturnType<typeof createDeliverabilityResource>;\n marketplace!: ReturnType<typeof createMarketplaceResource>;\n /** Custom domains for the hosted customer portal (live keys, behind a rollout flag). */\n hostedDomains!: ReturnType<typeof createHostedDomainsResource>;\n /**\n * Organization settings (Phase D3, F) - the configurable dunning schedule, usage settlement grace,\n * and the cross-currency reporting currency + FX-rate registry (ADR-0013).\n */\n settings!: ReturnType<typeof createSettingsResource>;\n\n constructor(apiKey: string, opts: BillowOptions = {}) {\n if (!apiKey) throw new Error(\"billow: an API key is required\");\n this.#ctx = makeContext(apiKey, opts);\n this.customers = createCustomersResource(this.#ctx);\n this.charges = createChargesResource(this.#ctx);\n this.checkoutMethods = createCheckoutMethodsResource(this.#ctx);\n this.credits = createCreditsResource(this.#ctx);\n this.invoices = createInvoicesResource(this.#ctx);\n this.metrics = createMetricsResource(this.#ctx);\n this.webhookEndpoints = createWebhookEndpointsResource(this.#ctx);\n this.products = createProductsResource(this.#ctx);\n this.subscriptions = createSubscriptionsResource(this.#ctx);\n this.portalSessions = createPortalSessionsResource(this.#ctx);\n this.deliverability = createDeliverabilityResource(this.#ctx);\n this.marketplace = createMarketplaceResource(this.#ctx);\n this.hostedDomains = createHostedDomainsResource(this.#ctx);\n this.settings = createSettingsResource(this.#ctx);\n }\n\n #request<T>(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<T> {\n return apiRequest<T>(this.#ctx, method, path, body, options);\n }\n\n coupons = {\n /** Define a coupon (a reusable discount template). */\n create: (input: CreateCouponInput) => this.#request<Coupon>(\"POST\", \"/v1/coupons\", input),\n /** List coupons (secret key only - enumerates live promo codes). Auto-paginating. */\n list: (params: ListParams = {}, options?: CallOptions): ListPromise<Coupon> =>\n makeListPromise(\n (p) =>\n this.#request<Paginated<Coupon>>(\"GET\", `/v1/coupons${toQuery(p)}`, undefined, options),\n params,\n ),\n /** Deactivate a coupon (existing discounts keep running). */\n deactivate: (id: string) =>\n this.#request<Coupon>(\"POST\", `/v1/coupons/${encodeURIComponent(id)}/deactivate`),\n };\n\n /** Manage developer webhook endpoints - register, rotate secrets, inspect deliveries.\n * (To VERIFY incoming deliveries, import `constructEvent` from `@usebillow/sdk/webhooks`.) */\n features = {\n /** Define a feature (a boolean access gate, or a metered feature with a meter). */\n create: (input: { slug: string; name: string; kind: FeatureKind; meter?: MeterConfig }) =>\n this.#request<Feature>(\"POST\", \"/v1/features\", input),\n /**\n * Edit a feature in place: its `name`, and - for a metered feature - its `meter`\n * aggregation. The server refuses a meter change once usage has been recorded (it\n * would rewrite billed history), so set the meter right at create time or before you\n * start tracking. Returns the updated feature.\n */\n update: (slug: string, input: UpdateFeatureInput) =>\n this.#request<Feature>(\"PATCH\", `/v1/features/${encodeURIComponent(slug)}`, input),\n /** List features in the tenant. Auto-paginating. */\n list: (params: ListParams = {}, options?: CallOptions): ListPromise<Feature> =>\n makeListPromise(\n (p) =>\n this.#request<Paginated<Feature>>(\"GET\", `/v1/features${toQuery(p)}`, undefined, options),\n params,\n ),\n /**\n * Usage Alerts + Spend caps on a metered feature (Phase C). A `threshold`\n * Alert notifies (webhook + optional customer email) as usage crosses its\n * thresholds; a `cap` blocks `check` once reached.\n */\n alerts: {\n /** List the alerts + spend cap on a metered feature (by slug). */\n list: (feature: string, options?: CallOptions) =>\n this.#request<{ alerts: UsageAlert[] }>(\n \"GET\",\n `/v1/features/${encodeURIComponent(feature)}/alerts`,\n undefined,\n options,\n ).then((r) => r.alerts),\n /** Define an Alert or Spend cap on a metered feature. */\n create: (feature: string, input: CreateUsageAlertInput) =>\n this.#request<UsageAlert>(\n \"POST\",\n `/v1/features/${encodeURIComponent(feature)}/alerts`,\n input,\n ),\n /** Delete one alert/cap rule by id. */\n remove: (feature: string, id: string) =>\n this.#request<{ removed: boolean }>(\n \"DELETE\",\n `/v1/features/${encodeURIComponent(feature)}/alerts/${encodeURIComponent(id)}`,\n ),\n },\n };\n\n integrations = {\n /** The registered payment providers + their config manifests (drives the picker + form). */\n providers: (options?: CallOptions) =>\n this.#request<{ providers: PaymentProviderDefinition[] }>(\n \"GET\",\n \"/v1/integrations/providers\",\n undefined,\n options,\n ).then((r) => r.providers),\n /** List configured credential sets per currency (provider + non-secret config). Secret-only. */\n list: (options?: CallOptions) =>\n this.#request<{ integrations: ProviderCredentialSummary[] }>(\n \"GET\",\n \"/v1/integrations\",\n undefined,\n options,\n ).then((r) => r.integrations),\n /**\n * Verify a provider's configuration against its live API. Pass `currency` to\n * choose a credential set and `target` to probe one verify target (Paymob: a\n * method); pass `sampleToken` (SANDBOX ONLY) to run a functional sub-test.\n * This probe hits the provider's live API (mutates nothing here), so it takes a\n * per-call {@link CallOptions} for a timeout / cancellation - `opts` is spread into\n * the body, so `options` stays a separate trailing arg (never merged in).\n */\n verify: (\n provider: string,\n opts?: { currency?: string; target?: string; sampleToken?: string },\n options?: CallOptions,\n ) =>\n this.#request<ProviderVerifyResult>(\n \"POST\",\n \"/v1/integrations/verify\",\n { provider, ...opts },\n options,\n ),\n /** Manage stored payment-provider credentials (the dashboard's \"manage credentials\" forms). */\n credentials: {\n /** The editable view of stored credential sets (secrets are never returned). */\n list: (options?: CallOptions) =>\n this.#request<{ credentials: CredentialSet[] }>(\n \"GET\",\n \"/v1/integrations/credentials\",\n undefined,\n options,\n ).then((r) => r.credentials),\n /** Create or rotate a (provider, currency) credential set. */\n upsert: (input: UpsertCredentialsInput) =>\n this.#request<CredentialSet>(\"PUT\", \"/v1/integrations/credentials\", input),\n /** Delete a (provider, currency) credential set. */\n remove: (provider: string, currency: string) =>\n this.#request<{ removed: boolean }>(\n \"DELETE\",\n `/v1/integrations/credentials/${encodeURIComponent(provider)}/${encodeURIComponent(currency)}`,\n ),\n },\n };\n\n /** The merchant's business identity - the seller block on documents and email from-name. */\n businessProfile = {\n /** The stored profile, or null if one was never saved. */\n get: (options?: CallOptions) =>\n this.#request<{ profile: BusinessProfile | null }>(\n \"GET\",\n \"/v1/business-profile\",\n undefined,\n options,\n ).then((r) => r.profile),\n /** Create or update the profile; omit a field to keep it, send \"\" to clear it. */\n update: (input: UpdateBusinessProfileInput) =>\n this.#request<{ profile: BusinessProfile }>(\"PUT\", \"/v1/business-profile\", input).then(\n (r) => r.profile,\n ),\n };\n\n /**\n * Hosted customer surfaces (Phase G, ADR-0014). Mint a portal session for one of\n * your signed-in users and redirect them to the returned `url` - the self-serve\n * portal, or a `checkout` hand-off for `productId`. The Customer's own calls go\n * through {@link BillowPortal}, constructed with the session token.\n */\n /** Identity of this key's tenant - org, environment, configured currencies. */\n me(options?: CallOptions): Promise<Whoami> {\n return this.#request<Whoami>(\"GET\", \"/v1/me\", undefined, options);\n }\n\n /**\n * Global search (PRD-12) across customers, invoices, subscriptions, and charges\n * for the key's `(project, environment)`. Bounded per entity (default 5, max 10);\n * a blank query returns empty results. Backs the dashboard's ⌘K palette.\n */\n search(q: string, limit?: number, options?: CallOptions): Promise<SearchResults> {\n return this.#request<SearchResults>(\n \"GET\",\n `/v1/search${toQuery({ q, limit })}`,\n undefined,\n options,\n );\n }\n\n // ── The headline verbs ──────────────────────────────────────────────\n\n /**\n * Gate access to a feature. `featureId` is the feature slug. Returns\n * `{ allowed, balance }` - `balance` is `null` for unlimited/boolean features.\n */\n check(\n input: { customerId: string; featureId: string },\n options?: CallOptions,\n ): Promise<CheckResult> {\n return this.#request<CheckResult>(\"POST\", \"/v1/check\", input, options);\n }\n\n /**\n * Record usage of a metered feature (`value` defaults to 1; negative credits\n * back). Pass `idempotencyKey` to make a retried call a no-op - which also makes the call\n * safe to retry automatically when `maxRetries` is set.\n */\n track(\n input: {\n customerId: string;\n featureId: string;\n value?: number;\n /** Arbitrary event properties - used by meter filters and property-based aggregations. */\n properties?: Record<string, string | number>;\n idempotencyKey?: string;\n /**\n * When the usage occurred (ISO-8601). Defaults to now; backdate it to land a value in a\n * still-open reset window (e.g. a finalized month-end total). Rejected if more than 5 minutes\n * in the future or more than 7 days in the past.\n */\n occurredAt?: string;\n },\n options?: CallOptions,\n ): Promise<TrackResult> {\n return this.#request<TrackResult>(\n \"POST\",\n \"/v1/track\",\n input,\n input.idempotencyKey ? { ...options, idempotencyKey: input.idempotencyKey } : options,\n );\n }\n\n /** List a customer's live entitlements (one per feature). */\n entitlements(\n customerId: string,\n options?: CallOptions,\n ): Promise<{ entitlements: EntitlementView[] }> {\n return this.#request(\n \"GET\",\n `/v1/customers/${encodeURIComponent(customerId)}/entitlements`,\n undefined,\n options,\n );\n }\n\n /** Start a subscription → returns a hosted checkout URL to redirect the customer to. */\n attach(input: CreateSubscriptionInput, options?: RequestOptions): Promise<AttachResult> {\n return this.#request<AttachResult>(\"POST\", \"/v1/attach\", input, options);\n }\n}\n\n/**\n * The browser-safe client: construct it with a **publishable** key (`bl_<env>_pk_…`)\n * to read the public catalog from any origin (CORS-enabled). It exposes ONLY public,\n * project-scoped reads - products and their prices - so it is safe to ship in a\n * frontend bundle. Customer-scoped data (entitlements, checkout) is never reachable\n * with a publishable key; use the server handler or a portal session for that.\n */\nexport class BillowPublishable {\n #ctx: RequestContext;\n\n constructor(publishableKey: string, opts: BillowOptions = {}) {\n if (!publishableKey) throw new Error(\"billow: a publishable key is required\");\n // Guard against shipping a SECRET key in a browser bundle: publishable keys look\n // like `bl_<env>_pk_…`. The catalog accepts any key, so a misplaced secret would\n // appear to work while leaking full API access.\n if (!publishableKey.includes(\"_pk_\")) {\n throw new Error(\n \"billow: BillowPublishable needs a publishable key (bl_<env>_pk_…), not a secret key\",\n );\n }\n this.#ctx = makeContext(publishableKey, opts);\n }\n\n products = {\n /** The active catalog - products and their prices, for a pricing table. */\n list: (options?: CallOptions) =>\n apiRequest<{ products: Product[] }>(\n this.#ctx,\n \"GET\",\n \"/v1/products\",\n undefined,\n options,\n ).then((r) => r.products),\n /** Fetch one active product (with its prices) by slug. */\n get: (slug: string, options?: CallOptions) =>\n apiRequest<Product>(\n this.#ctx,\n \"GET\",\n `/v1/products/${encodeURIComponent(slug)}`,\n undefined,\n options,\n ),\n };\n}\n\n/**\n * The customer-surface client (Phase G, ADR-0014): construct it with a portal\n * session token (minted by your backend via `billow.portalSessions.create`) to act\n * as that one Customer. It exposes only their own data - no operator/secret-key\n * surface - so the token can be held by a hosted portal app safely.\n */\nexport class BillowPortal {\n #ctx: RequestContext;\n\n constructor(sessionToken: string, opts: BillowOptions = {}) {\n if (!sessionToken) throw new Error(\"billow: a portal session token is required\");\n this.#ctx = makeContext(sessionToken, opts);\n }\n\n /** The portal shell: flow, return URL, merchant brand, and customer identity -\n * the lightweight payload the hosting app frames every page with, and the\n * validate-and-route check at login. */\n session(options?: CallOptions): Promise<PortalSessionInfo> {\n return apiRequest<PortalSessionInfo>(this.#ctx, \"GET\", \"/portal/session\", undefined, options);\n }\n\n /**\n * The single-use entry exchange (ADR-0039): on a client built with a minted link's entry\n * token, trade it for the session token to use from now on. Succeeds once per link, and\n * afterwards only for the browser presenting that exchange's `sessionToken`; anything else is\n * a 401. For a hosted surface; an integrator's own surface may keep using the entry token.\n */\n exchange(\n input: PortalSessionExchangeInput,\n options?: CallOptions,\n ): Promise<PortalSessionExchange> {\n return apiRequest<PortalSessionExchange>(\n this.#ctx,\n \"POST\",\n \"/portal/session/exchange\",\n input,\n options,\n );\n }\n\n /** The self-serve home payload: identity + subscriptions + usage + saved cards. */\n me(options?: CallOptions): Promise<PortalView> {\n return apiRequest<PortalView>(this.#ctx, \"GET\", \"/portal/me\", undefined, options);\n }\n\n invoices = {\n /** The Customer's invoices (newest first). Auto-paginating. */\n list: (\n params: ListParams & { status?: InvoiceListItem[\"status\"] } = {},\n options?: CallOptions,\n ): ListPromise<InvoiceListItem> =>\n makeListPromise(\n (p) =>\n apiRequest<Paginated<InvoiceListItem>>(\n this.#ctx,\n \"GET\",\n `/portal/invoices${toQuery(p)}`,\n undefined,\n options,\n ),\n params,\n ),\n /** One of the Customer's invoices, in the list's shape. */\n get: (id: string, options?: CallOptions) =>\n apiRequest<InvoiceListItem>(\n this.#ctx,\n \"GET\",\n `/portal/invoices/${encodeURIComponent(id)}`,\n undefined,\n options,\n ),\n /** Open or resume checkout for this customer's outstanding invoice. */\n pay: (id: string, input: PayInvoiceInput = {}, options?: CallOptions) =>\n apiRequest<InvoicePaymentResult>(\n this.#ctx,\n \"POST\",\n `/portal/invoices/${encodeURIComponent(id)}/pay`,\n input,\n options,\n ),\n /** Download one of the Customer's invoices as a PDF (raw bytes). */\n pdf: (id: string, options?: CallOptions) =>\n apiRequestBinary(this.#ctx, \"GET\", `/portal/invoices/${encodeURIComponent(id)}/pdf`, options),\n /** Download the payment receipt for one of the Customer's invoices (raw bytes). */\n receiptPdf: (id: string, options?: CallOptions) =>\n apiRequestBinary(\n this.#ctx,\n \"GET\",\n `/portal/invoices/${encodeURIComponent(id)}/receipt.pdf`,\n options,\n ),\n };\n\n subscriptions = {\n /** Schedule one of the Customer's subscriptions to cancel at period end. */\n cancel: (id: string) =>\n apiRequest<PortalSubscriptionStatus>(\n this.#ctx,\n \"POST\",\n `/portal/subscriptions/${encodeURIComponent(id)}/cancel`,\n ),\n /** Reverse a scheduled period-end cancellation. */\n resumeCancellation: (id: string) =>\n apiRequest<PortalSubscriptionStatus>(\n this.#ctx,\n \"POST\",\n `/portal/subscriptions/${encodeURIComponent(id)}/resume-cancellation`,\n ),\n /** Move one of the Customer's subscriptions to another plan (upgrade now / downgrade scheduled). */\n changePlan: (id: string, productId: string) =>\n apiRequest<PortalPlanChange>(\n this.#ctx,\n \"POST\",\n `/portal/subscriptions/${encodeURIComponent(id)}/plan`,\n { productId },\n ),\n /** Clear a pending downgrade. */\n clearScheduledPlanChange: (id: string) =>\n apiRequest<PortalSubscriptionStatus>(\n this.#ctx,\n \"DELETE\",\n `/portal/subscriptions/${encodeURIComponent(id)}/plan`,\n ),\n };\n\n paymentMethod = {\n /** Open a checkout to save or replace the Customer's card (zero-amount tokenization). */\n update: (returnUrl?: string) =>\n apiRequest<PortalCardSetupResult>(\n this.#ctx,\n \"POST\",\n \"/portal/payment-method/update\",\n returnUrl ? { returnUrl } : {},\n ),\n };\n\n paymentMethods = {\n list: (options?: CallOptions) =>\n apiRequest<{ paymentMethods: SavedPaymentMethod[] }>(\n this.#ctx,\n \"GET\",\n \"/portal/payment-methods\",\n undefined,\n options,\n ).then((r) => r.paymentMethods),\n setDefault: (id: string) =>\n apiRequest<SavedPaymentMethod>(\n this.#ctx,\n \"POST\",\n `/portal/payment-methods/${encodeURIComponent(id)}/default`,\n ),\n remove: (id: string) =>\n apiRequest<{ removed: boolean }>(\n this.#ctx,\n \"DELETE\",\n `/portal/payment-methods/${encodeURIComponent(id)}`,\n ),\n };\n\n /** The checkout hand-off, for a session minted with `flow: \"checkout\"`. */\n checkout = {\n /** The plan being confirmed (product + base price). */\n get: (options?: CallOptions) =>\n apiRequest<PortalCheckout>(this.#ctx, \"GET\", \"/portal/checkout\", undefined, options),\n /** Subscribe to the plan → returns the hosted Paymob checkout URL to redirect to. */\n start: () => apiRequest<{ checkoutUrl: string | null }>(this.#ctx, \"POST\", \"/portal/checkout\"),\n };\n}\n","\"use client\";\n\n/**\n * React adapter (ARCHITECTURE §6: \"framework adapters - React hooks first\"). These\n * hooks talk to the same-origin billow handler (`@usebillow/sdk/server`), never to billow\n * directly - the secret key stays on your server and the customer is resolved from\n * your session. Wrap your app in `<BillowProvider>` and use the hooks below.\n *\n * <BillowProvider>\n * <App />\n * </BillowProvider>\n *\n * const { allowed } = useCheck(\"projects\");\n * const attach = useAttach();\n * <button onClick={() => attach({ productId: \"pro\" }, { redirect: true })}>Upgrade</button>\n */\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport type { AttachResult, CheckResult, EntitlementView, Product, TrackResult } from \"./index\";\nimport { BillowPublishable } from \"./index\";\n\ninterface BillowContextValue {\n request: <T>(method: string, path: string, body?: unknown) => Promise<T>;\n entitlements: EntitlementView[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n}\n\nconst BillowContext = createContext<BillowContextValue | null>(null);\n\nexport interface BillowProviderProps {\n children: ReactNode;\n /** Where the billow handler is mounted. Default `/api/billow`. */\n basePath?: string;\n /** Custom fetch (SSR/testing). Defaults to the global `fetch`. */\n fetcher?: typeof fetch;\n}\n\nexport function BillowProvider({\n children,\n basePath = \"/api/billow\",\n fetcher,\n}: BillowProviderProps) {\n const base = basePath.replace(/\\/$/, \"\");\n const doFetch = fetcher ?? (globalThis.fetch as typeof fetch);\n\n const request = useCallback(\n async <T,>(method: string, path: string, body?: unknown): Promise<T> => {\n const init: RequestInit = { method };\n if (body) {\n init.headers = { \"content-type\": \"application/json\" };\n init.body = JSON.stringify(body);\n }\n const res = await doFetch(`${base}${path}`, init);\n const data = (await res.json().catch(() => ({}))) as { error?: { message?: string } };\n if (!res.ok) {\n throw new Error(data.error?.message ?? `billow ${method} ${path} failed (${res.status})`);\n }\n return data as T;\n },\n [base, doFetch],\n );\n\n const [entitlements, setEntitlements] = useState<EntitlementView[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const data = await request<{ entitlements: EntitlementView[] }>(\"GET\", \"/entitlements\");\n setEntitlements(data.entitlements);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [request]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const value = useMemo<BillowContextValue>(\n () => ({ request, entitlements, isLoading, error, refresh }),\n [request, entitlements, isLoading, error, refresh],\n );\n\n return <BillowContext.Provider value={value}>{children}</BillowContext.Provider>;\n}\n\nfunction useBillowContext(): BillowContextValue {\n const ctx = useContext(BillowContext);\n if (!ctx) throw new Error(\"billow: wrap your app in <BillowProvider>\");\n return ctx;\n}\n\n/** The identified customer's live entitlements (one per granted feature), with a `refresh`. */\nexport function useEntitlements(): {\n entitlements: EntitlementView[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n} {\n const { entitlements, isLoading, error, refresh } = useBillowContext();\n return { entitlements, isLoading, error, refresh };\n}\n\n/**\n * Gate a feature, derived client-side from the cached entitlements (no round-trip).\n * `error` is set when the entitlements fetch failed - distinguish that from a real\n * `allowed: false` (otherwise an auth/network failure looks like a denied feature).\n */\nexport function useCheck(\n featureId: string,\n): CheckResult & { isLoading: boolean; error: Error | null } {\n const { entitlements, isLoading, error } = useBillowContext();\n const match = entitlements.find((e) => e.feature === featureId);\n return {\n feature: featureId,\n allowed: match?.allowed ?? false,\n balance: match?.balance ?? null,\n isLoading,\n error,\n };\n}\n\n/** Start a subscription → checkout URL. Pass `{ redirect: true }` to navigate to it. */\nexport function useAttach(): (\n input: { productId: string; couponCode?: string },\n opts?: { redirect?: boolean },\n) => Promise<AttachResult> {\n const { request, refresh } = useBillowContext();\n return useCallback(\n async (input, opts = {}) => {\n const result = await request<AttachResult>(\"POST\", \"/attach\", input);\n await refresh();\n const loc = (globalThis as { location?: { href: string } }).location;\n if (opts.redirect && result.url && loc) loc.href = result.url;\n return result;\n },\n [request, refresh],\n );\n}\n\n/**\n * Record usage from the browser. Requires the handler's `allowClientTrack` - most\n * apps should `track` server-side instead (a browser caller can forge usage).\n */\nexport function useTrack(): (input: {\n featureId: string;\n value?: number;\n idempotencyKey?: string;\n}) => Promise<TrackResult> {\n const { request, refresh } = useBillowContext();\n return useCallback(\n async (input) => {\n const result = await request<TrackResult>(\"POST\", \"/track\", input);\n await refresh();\n return result;\n },\n [request, refresh],\n );\n}\n\n/** Fetch the public product catalog (for a pricing table). */\nexport function useProducts(): { products: Product[]; isLoading: boolean; error: Error | null } {\n const { request } = useBillowContext();\n const [products, setProducts] = useState<Product[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n let active = true;\n setIsLoading(true);\n request<{ products: Product[] }>(\"GET\", \"/products\")\n .then((r) => {\n if (!active) return;\n setProducts(r.products);\n setError(null);\n })\n .catch((err) => {\n if (active) setError(err instanceof Error ? err : new Error(String(err)));\n })\n .finally(() => {\n if (active) setIsLoading(false);\n });\n return () => {\n active = false;\n };\n }, [request]);\n\n return { products, isLoading, error };\n}\n\nexport interface PublishableConfig {\n /** A publishable key (`bl_<env>_pk_…`) - safe to ship in a frontend bundle. */\n publishableKey: string;\n /** The Billow API base URL. Defaults to the hosted API, `https://api.usebillow.com`. */\n baseUrl?: string;\n}\n\n/**\n * Drop-in catalog reader for a pure-SPA pricing table: fetches the public products +\n * prices directly from the browser with a publishable key (CORS-enabled), no backend\n * handler required. Reads only public data - customer entitlements/checkout still need\n * the server handler or a portal session.\n */\nexport function usePublishableProducts(config: PublishableConfig): {\n products: Product[];\n isLoading: boolean;\n error: string | null;\n} {\n const { publishableKey, baseUrl } = config;\n const [products, setProducts] = useState<Product[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let active = true;\n setIsLoading(true);\n const client = new BillowPublishable(publishableKey, baseUrl ? { baseUrl } : {});\n client.products.list().then(\n (list) => {\n if (!active) return;\n setProducts(list);\n setError(null);\n setIsLoading(false);\n },\n (e: unknown) => {\n if (!active) return;\n setError(e instanceof Error ? e.message : String(e));\n setIsLoading(false);\n },\n );\n return () => {\n active = false;\n };\n }, [publishableKey, baseUrl]);\n\n return { products, isLoading, error };\n}\n"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/index.ts","../src/react.tsx"],"names":["safeToRepeat","createContext","useCallback","useState","useEffect","useMemo","jsx","useContext"],"mappings":"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","file":"react.cjs","sourcesContent":["import type { BillowErrorCode } from \"./error-codes\";\nimport type { BillowOptions, CallOptions, RequestOptions } from \"./types\";\n\n/** The response header carrying billow's per-request id (echoed on success and error alike). */\nconst REQUEST_ID_HEADER = \"x-request-id\";\n\n/** Build a `?a=b&c=d` string, skipping undefined/null/empty values. */\nexport function toQuery(params: object): string {\n const q = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null && v !== \"\") q.set(k, String(v));\n }\n const s = q.toString();\n return s ? `?${s}` : \"\";\n}\n\nexport class BillowApiError extends Error {\n readonly status: number;\n /**\n * A known {@link BillowErrorCode} (widened to `string` so a newer server's code still type-checks),\n * or one the SDK raises itself: `invalid_response` for a success (2xx other than 204) whose body\n * was empty or could not be read or parsed. The request reached billow and was answered, but what\n * it answered is unknown - for a write, treat the outcome as UNKNOWN (retry it under the same\n * idempotency key), never as done or as refused.\n */\n readonly code: BillowErrorCode | (string & {});\n /** Structured error context from the server (e.g. per-field validation issues), when present. */\n readonly details?: unknown;\n /** billow's per-request id (`x-request-id`) - quote it in a bug report to trace the server log. */\n readonly requestId?: string | undefined;\n /**\n * How many seconds to wait before trying again, from the response's `Retry-After` header (sent\n * with a `429 rate_limited`); undefined when the response did not say.\n */\n readonly retryAfterSeconds?: number | undefined;\n\n constructor(\n status: number,\n code: BillowErrorCode | (string & {}),\n message: string,\n details?: unknown,\n requestId?: string,\n retryAfterSeconds?: number,\n ) {\n super(message);\n this.name = \"BillowApiError\";\n this.status = status;\n this.code = code;\n this.details = details;\n this.requestId = requestId;\n this.retryAfterSeconds = retryAfterSeconds;\n }\n}\n\n/** Hard per-request timeout when a caller doesn't set one. */\nconst DEFAULT_TIMEOUT_MS = 60_000;\n/** Base retry backoff when a caller doesn't set one. */\nconst DEFAULT_RETRY_BACKOFF_MS = 500;\n/** The longest wait a `Retry-After` is taken at: anything longer reads as an hour. */\nconst MAX_RETRY_AFTER_SECONDS = 3600;\n\n/** Everything a request needs: the transport, the base URL, the bearer, and resilience knobs. */\nexport interface RequestContext {\n fetch: typeof fetch;\n baseUrl: string;\n /** The bearer - a secret/publishable API key, or a portal-session token. */\n bearer: string;\n timeoutMs: number;\n maxRetries: number;\n retryBackoffMs: number;\n}\n\nexport function makeContext(bearer: string, opts: BillowOptions): RequestContext {\n return {\n fetch: opts.fetch ?? fetch,\n baseUrl: resolveBaseUrl(opts.baseUrl),\n bearer,\n timeoutMs: opts.timeoutMs ?? DEFAULT_TIMEOUT_MS,\n maxRetries: opts.maxRetries ?? 0,\n retryBackoffMs: opts.retryBackoffMs ?? DEFAULT_RETRY_BACKOFF_MS,\n };\n}\n\n/**\n * Whether a failed attempt is safe to retry. A `null` status is a network error or timeout\n * (no response). A `429` never executed (rate-limited), so it is safe to repeat regardless; a\n * network error / `5xx` might have applied a write, so those retry only when the request is\n * `safeToRepeat` - an idempotent method (`GET`/`HEAD`) or an idempotency-keyed write.\n */\nfunction isRetryable(status: number | null, safeToRepeat: boolean): boolean {\n if (status === 429) return true;\n if (status === null) return safeToRepeat;\n return safeToRepeat && status >= 500 && status <= 599;\n}\n\n/**\n * Backoff before the next attempt: honor `Retry-After` (seconds, at most an hour) if given, else\n * exponential.\n */\nfunction backoffMs(base: number, attempt: number, retryAfter: string | null): number {\n if (retryAfter) {\n const secs = Number(retryAfter);\n if (Number.isFinite(secs) && secs >= 0) return Math.min(secs, MAX_RETRY_AFTER_SECONDS) * 1000;\n }\n return base * 2 ** attempt;\n}\n\n/** The reason an aborted signal carries, or a plain `AbortError` when none was given. */\nfunction abortReason(signal: AbortSignal): unknown {\n return signal.reason ?? new DOMException(\"The operation was aborted.\", \"AbortError\");\n}\n\n/** A backoff sleep that a caller's abort can cut short. */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) return reject(abortReason(signal));\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(abortReason(signal));\n },\n { once: true },\n );\n });\n}\n\ninterface SendOptions {\n timeoutMs: number;\n /** Retry a network error / `5xx` (a `429` always retries) - set for GET/HEAD or keyed writes. */\n safeToRepeat: boolean;\n /** The caller's cancellation signal, if any. A caller abort is surfaced at once, never retried. */\n callerSignal: AbortSignal | undefined;\n}\n\n/**\n * Send one request with a per-attempt timeout and opt-in retries. Returns the final `Response`\n * (which may carry a non-retryable error status for the caller to parse); throws on a network\n * error / timeout that leaves no response, or immediately with the caller's reason if the\n * caller's `signal` aborts. Retryable responses are discarded and re-issued up to `maxRetries`\n * with {@link backoffMs} between attempts. The timeout is applied **per attempt**.\n */\nasync function sendWithResilience(\n ctx: RequestContext,\n url: string,\n init: RequestInit,\n opts: SendOptions,\n): Promise<Response> {\n const { timeoutMs, safeToRepeat, callerSignal } = opts;\n if (callerSignal?.aborted) throw abortReason(callerSignal);\n\n for (let attempt = 0; ; attempt++) {\n // A fresh controller per attempt, aborted by either the per-attempt timeout or the caller.\n const controller = new AbortController();\n let timedOut = false;\n const onCallerAbort = () => controller.abort(abortReason(callerSignal as AbortSignal));\n callerSignal?.addEventListener(\"abort\", onCallerAbort, { once: true });\n const timer =\n timeoutMs > 0\n ? setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs)\n : undefined;\n\n let res: Response | null = null;\n let err: unknown;\n try {\n res = await ctx.fetch(url, { ...init, signal: controller.signal });\n } catch (e) {\n err = e;\n } finally {\n if (timer) clearTimeout(timer);\n callerSignal?.removeEventListener(\"abort\", onCallerAbort);\n }\n\n // The caller cancelled - surface their reason immediately, never retry.\n if (callerSignal?.aborted) throw abortReason(callerSignal);\n\n if (res?.ok) return res;\n const status = res ? res.status : null;\n if (attempt < ctx.maxRetries && isRetryable(status, safeToRepeat)) {\n await sleep(\n backoffMs(ctx.retryBackoffMs, attempt, res?.headers.get(\"retry-after\") ?? null),\n callerSignal,\n );\n continue;\n }\n if (res) return res; // non-retryable (or retries exhausted) HTTP error - caller parses the body\n if (timedOut) throw new Error(`billow request timed out after ${timeoutMs}ms`);\n throw err instanceof Error ? err : new Error(\"billow request failed\");\n }\n}\n\n/** A method with no request body is inherently safe to repeat; a write needs an idempotency key. */\nfunction safeToRepeat(method: string, hasIdempotencyKey: boolean): boolean {\n return method === \"GET\" || method === \"HEAD\" || hasIdempotencyKey;\n}\n\n/** The hosted Billow API, which a client talks to unless told otherwise. */\nexport const DEFAULT_BASE_URL = \"https://api.usebillow.com\";\n\n/**\n * Resolve the API base URL: the explicit `baseUrl` option first, then the `BILLOW_URL`\n * environment variable (Node), then the hosted API. Local development and self-hosted\n * servers set one of the first two. Trailing slash stripped.\n */\nfunction resolveBaseUrl(explicit?: string): string {\n // biome-ignore lint/complexity/useLiteralKeys: the SDK cannot augment every consumer's ProcessEnv.\n const fromEnv = typeof process !== \"undefined\" ? process.env?.[\"BILLOW_URL\"] : undefined;\n return (explicit || fromEnv || DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n}\n\nexport async function apiRequest<T>(\n ctx: RequestContext,\n method: string,\n path: string,\n body?: unknown,\n options?: RequestOptions,\n): Promise<T> {\n return (await apiRequestWithStatus<T>(ctx, method, path, body, options)).data;\n}\n\n/**\n * {@link apiRequest}, also answering the success status - for the writes whose status carries\n * meaning (an idempotent create answers `201` when it created, `200` when it replayed).\n */\nexport async function apiRequestWithStatus<T>(\n ctx: RequestContext,\n method: string,\n path: string,\n body?: unknown,\n options?: RequestOptions,\n): Promise<{ status: number; data: T }> {\n const init: RequestInit = {\n method,\n headers: {\n authorization: `Bearer ${ctx.bearer}`,\n ...(body ? { \"content-type\": \"application/json\" } : {}),\n ...(options?.idempotencyKey ? { \"idempotency-key\": options.idempotencyKey } : {}),\n },\n };\n if (body) init.body = JSON.stringify(body);\n const res = await sendWithResilience(ctx, `${ctx.baseUrl}${path}`, init, {\n timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,\n safeToRepeat: safeToRepeat(method, !!options?.idempotencyKey),\n callerSignal: options?.signal,\n });\n // null: the body could not be read (the connection dropped mid-body, say).\n const text = await res.text().catch(() => null);\n if (!res.ok) throw errorFrom(res, safeParse(text ?? \"\"));\n return { status: res.status, data: parseSuccessBody<T>(res, text) };\n}\n\n/** billow's per-request id from a response, for {@link BillowApiError.requestId}. */\nfunction requestIdOf(res: Response): string | undefined {\n return res.headers.get(REQUEST_ID_HEADER) ?? undefined;\n}\n\n/** Parse leniently - a malformed *error* body still yields the HTTP-status error, not a parse error. */\nfunction safeParse(text: string): unknown {\n if (text.trim() === \"\") return {};\n try {\n return JSON.parse(text);\n } catch {\n return {};\n }\n}\n\n/**\n * A successful response must carry valid JSON; only a `204 No Content` (a delete) carries none. An\n * empty, unreadable or malformed 2xx body is an `invalid_response` error, never a silently-empty\n * `{}` - which a caller would take for an answer (a write it thinks done, with `undefined` fields).\n */\nfunction parseSuccessBody<T>(res: Response, text: string | null): T {\n if (res.status === 204) return {} as T;\n if (text !== null && text.trim() !== \"\") {\n try {\n return JSON.parse(text) as T;\n } catch {\n // Falls through to the error below.\n }\n }\n throw new BillowApiError(\n res.status,\n \"invalid_response\",\n `billow returned an empty or unreadable response body (HTTP ${res.status})`,\n undefined,\n requestIdOf(res),\n );\n}\n\n/** Build a {@link BillowApiError} from a failed response (surfacing `details` + the `requestId`). */\nfunction errorFrom(res: Response, data: unknown): BillowApiError {\n const err =\n (data as { error?: { code?: string; message?: string; details?: unknown } }).error ?? {};\n return new BillowApiError(\n res.status,\n err.code ?? \"error\",\n err.message ?? String(res.status),\n err.details,\n requestIdOf(res),\n retryAfterOf(res),\n );\n}\n\n/** An HTTP date in its preferred form (IMF-fixdate, RFC 9110): `Sun, 06 Nov 1994 08:49:37 GMT`. */\nconst IMF_FIXDATE = /^[A-Z][a-z]{2}, \\d{2} [A-Z][a-z]{2} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT$/;\n\n/**\n * The seconds a response's `Retry-After` asks a client to wait: whole seconds, or an HTTP date\n * counted from now (never below 0), at most an hour. Undefined when absent or unreadable: a value\n * that is neither (a fraction, a sign, a date in another form) is not guessed at.\n */\nfunction retryAfterOf(res: Response): number | undefined {\n const value = res.headers.get(\"retry-after\")?.trim();\n if (!value) return undefined;\n let seconds: number;\n if (/^\\d+$/.test(value)) {\n seconds = Number(value);\n } else if (IMF_FIXDATE.test(value)) {\n seconds = Math.ceil((Date.parse(value) - Date.now()) / 1000);\n } else {\n return undefined;\n }\n if (!Number.isFinite(seconds)) return undefined;\n return Math.min(Math.max(0, seconds), MAX_RETRY_AFTER_SECONDS);\n}\n\n/** Fetch a binary endpoint (e.g. a PDF) as raw bytes (a valid `BodyInit`). */\nexport async function apiRequestBinary(\n ctx: RequestContext,\n method: string,\n path: string,\n options?: CallOptions,\n): Promise<ArrayBuffer> {\n const res = await sendWithResilience(\n ctx,\n `${ctx.baseUrl}${path}`,\n { method, headers: { authorization: `Bearer ${ctx.bearer}` } },\n {\n timeoutMs: options?.timeoutMs ?? ctx.timeoutMs,\n safeToRepeat: safeToRepeat(method, false),\n callerSignal: options?.signal,\n },\n );\n if (!res.ok) throw errorFrom(res, safeParse(await res.text().catch(() => \"\")));\n return res.arrayBuffer();\n}\n","/**\n * billow client SDK - the \"two lines of code\" surface, mirroring Autumn's shape\n * (CONTEXT/ARCHITECTURE §10). A thin typed wrapper over the billow REST API.\n *\n * const billow = new Billow(process.env.BILLOW_SECRET_KEY!);\n * const { allowed } = await billow.check({ customerId, featureId: \"projects\" });\n *\n * Public request/response types live in ./types and are re-exported here, so the\n * package root stays the single import surface. They are contract-derived and inlined\n * into the shipped .d.ts at build time (tsup `dts.resolve` reads @billow/contracts'\n * bundled dist), so the declarations carry no bare `@billow/contracts` import a\n * consumer couldn't resolve.\n *\n * We deliberately do NOT re-export @billow/contracts' runtime zod schemas: a client\n * SDK shouldn't ship validation - the server is the schema authority - and\n * re-exporting them made @billow/contracts (a private, unpublished workspace package)\n * a runtime dependency that left the published SDK impossible to install. Contracts is\n * now a devDependency. The types are zod-inferred, so the declarations do reference\n * `zod` (a small, published package kept as a runtime dependency); the runtime bundle\n * itself imports neither zod nor @billow/contracts.\n */\n\nexport type {\n AddSubscriptionItemInput as ContractAddSubscriptionItemInput,\n ApplySubscriptionCouponInput as ContractApplySubscriptionCouponInput,\n CancelSubscriptionInput as ContractCancelSubscriptionInput,\n ChangeSubscriptionPlanInput as ContractChangeSubscriptionPlanInput,\n CreateChargeInput as ContractCreateChargeInput,\n CreateCustomerInput as ContractCreateCustomerInput,\n CreateMerchantInitiatedChargeInput as ContractCreateMerchantInitiatedChargeInput,\n CreatePortalSessionInput as ContractCreatePortalSessionInput,\n CreateProductInput as ContractCreateProductInput,\n CreateSubscriptionInput as ContractCreateSubscriptionInput,\n CreateWebhookEndpointInput as ContractCreateWebhookEndpointInput,\n MigrateSubscribersInput as ContractMigrateSubscribersInput,\n RefundChargeInput as ContractRefundChargeInput,\n ResolveRefundInput as ContractResolveRefundInput,\n UpdateProductInput as ContractUpdateProductInput,\n UpdateSubscriptionItemInput as ContractUpdateSubscriptionItemInput,\n UpdateWebhookEndpointInput as ContractUpdateWebhookEndpointInput,\n} from \"@billow/contracts\";\nexport * from \"./billing-status\";\nexport { BillowApiError } from \"./client\";\nexport * from \"./error-codes\";\nexport * from \"./money\";\nexport * from \"./subscription-status\";\nexport * from \"./types\";\n\nimport { apiRequest, apiRequestBinary, makeContext, type RequestContext, toQuery } from \"./client\";\nimport { makeListPromise } from \"./pagination\";\nimport { createChargesResource, createCheckoutMethodsResource } from \"./resources/charges\";\nimport { createCreditsResource } from \"./resources/credits\";\nimport { createCustomersResource } from \"./resources/customers\";\nimport { createDeliverabilityResource } from \"./resources/deliverability\";\nimport { createHostedDomainsResource } from \"./resources/hosted-domains\";\nimport { createInvoicesResource } from \"./resources/invoices\";\nimport { createMarketplaceResource } from \"./resources/marketplace\";\nimport { createMetricsResource } from \"./resources/metrics\";\nimport { createPortalSessionsResource } from \"./resources/portal-sessions\";\nimport { createProductsResource } from \"./resources/products\";\nimport { createSettingsResource } from \"./resources/settings\";\nimport { createSubscriptionsResource } from \"./resources/subscriptions\";\nimport { createWebhookEndpointsResource } from \"./resources/webhook-endpoints\";\nimport type {\n AttachResult,\n BillowOptions,\n BusinessProfile,\n CallOptions,\n CheckResult,\n Coupon,\n CreateCouponInput,\n CreateSubscriptionInput,\n CreateUsageAlertInput,\n CredentialSet,\n EntitlementView,\n Feature,\n FeatureKind,\n InvoiceListItem,\n InvoicePaymentResult,\n ListParams,\n ListPromise,\n MeterConfig,\n Paginated,\n PayInvoiceInput,\n PaymentProviderDefinition,\n PortalCardSetupResult,\n PortalCheckout,\n PortalCheckoutStartInput,\n PortalPlanChange,\n PortalSessionExchange,\n PortalSessionExchangeInput,\n PortalSessionInfo,\n PortalSubscriptionStatus,\n PortalView,\n Product,\n ProviderCredentialSummary,\n ProviderVerifyResult,\n RequestOptions,\n SavedPaymentMethod,\n SearchResults,\n TrackResult,\n UpdateBusinessProfileInput,\n UpdateFeatureInput,\n UpsertCredentialsInput,\n UsageAlert,\n Whoami,\n} from \"./types\";\n\nexport class Billow {\n #ctx: RequestContext;\n\n customers!: ReturnType<typeof createCustomersResource>;\n charges!: ReturnType<typeof createChargesResource>;\n checkoutMethods!: ReturnType<typeof createCheckoutMethodsResource>;\n /** Prepaid credits: grant them to a Customer (quantities are decimal strings of microcredits). */\n credits!: ReturnType<typeof createCreditsResource>;\n invoices!: ReturnType<typeof createInvoicesResource>;\n metrics!: ReturnType<typeof createMetricsResource>;\n webhookEndpoints!: ReturnType<typeof createWebhookEndpointsResource>;\n products!: ReturnType<typeof createProductsResource>;\n subscriptions!: ReturnType<typeof createSubscriptionsResource>;\n portalSessions!: ReturnType<typeof createPortalSessionsResource>;\n deliverability!: ReturnType<typeof createDeliverabilityResource>;\n marketplace!: ReturnType<typeof createMarketplaceResource>;\n /** Custom domains for the hosted customer portal (live keys, behind a rollout flag). */\n hostedDomains!: ReturnType<typeof createHostedDomainsResource>;\n /**\n * Organization settings (Phase D3, F) - the configurable dunning schedule, usage settlement grace,\n * and the cross-currency reporting currency + FX-rate registry (ADR-0013).\n */\n settings!: ReturnType<typeof createSettingsResource>;\n\n constructor(apiKey: string, opts: BillowOptions = {}) {\n if (!apiKey) throw new Error(\"billow: an API key is required\");\n this.#ctx = makeContext(apiKey, opts);\n this.customers = createCustomersResource(this.#ctx);\n this.charges = createChargesResource(this.#ctx);\n this.checkoutMethods = createCheckoutMethodsResource(this.#ctx);\n this.credits = createCreditsResource(this.#ctx);\n this.invoices = createInvoicesResource(this.#ctx);\n this.metrics = createMetricsResource(this.#ctx);\n this.webhookEndpoints = createWebhookEndpointsResource(this.#ctx);\n this.products = createProductsResource(this.#ctx);\n this.subscriptions = createSubscriptionsResource(this.#ctx);\n this.portalSessions = createPortalSessionsResource(this.#ctx);\n this.deliverability = createDeliverabilityResource(this.#ctx);\n this.marketplace = createMarketplaceResource(this.#ctx);\n this.hostedDomains = createHostedDomainsResource(this.#ctx);\n this.settings = createSettingsResource(this.#ctx);\n }\n\n #request<T>(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<T> {\n return apiRequest<T>(this.#ctx, method, path, body, options);\n }\n\n coupons = {\n /** Define a coupon (a reusable discount template). */\n create: (input: CreateCouponInput) => this.#request<Coupon>(\"POST\", \"/v1/coupons\", input),\n /** List coupons (secret key only - enumerates live promo codes). Auto-paginating. */\n list: (params: ListParams = {}, options?: CallOptions): ListPromise<Coupon> =>\n makeListPromise(\n (p) =>\n this.#request<Paginated<Coupon>>(\"GET\", `/v1/coupons${toQuery(p)}`, undefined, options),\n params,\n ),\n /** Deactivate a coupon (existing discounts keep running). */\n deactivate: (id: string) =>\n this.#request<Coupon>(\"POST\", `/v1/coupons/${encodeURIComponent(id)}/deactivate`),\n };\n\n /** Manage developer webhook endpoints - register, rotate secrets, inspect deliveries.\n * (To VERIFY incoming deliveries, import `constructEvent` from `@usebillow/sdk/webhooks`.) */\n features = {\n /** Define a feature (a boolean access gate, or a metered feature with a meter). */\n create: (input: { slug: string; name: string; kind: FeatureKind; meter?: MeterConfig }) =>\n this.#request<Feature>(\"POST\", \"/v1/features\", input),\n /**\n * Edit a feature in place: its `name`, and - for a metered feature - its `meter`\n * aggregation. The server refuses a meter change once usage has been recorded (it\n * would rewrite billed history), so set the meter right at create time or before you\n * start tracking. Returns the updated feature.\n */\n update: (slug: string, input: UpdateFeatureInput) =>\n this.#request<Feature>(\"PATCH\", `/v1/features/${encodeURIComponent(slug)}`, input),\n /** List features in the tenant. Auto-paginating. */\n list: (params: ListParams = {}, options?: CallOptions): ListPromise<Feature> =>\n makeListPromise(\n (p) =>\n this.#request<Paginated<Feature>>(\"GET\", `/v1/features${toQuery(p)}`, undefined, options),\n params,\n ),\n /**\n * Usage Alerts + Spend caps on a metered feature (Phase C). A `threshold`\n * Alert notifies (webhook + optional customer email) as usage crosses its\n * thresholds; a `cap` blocks `check` once reached.\n */\n alerts: {\n /** List the alerts + spend cap on a metered feature (by slug). */\n list: (feature: string, options?: CallOptions) =>\n this.#request<{ alerts: UsageAlert[] }>(\n \"GET\",\n `/v1/features/${encodeURIComponent(feature)}/alerts`,\n undefined,\n options,\n ).then((r) => r.alerts),\n /** Define an Alert or Spend cap on a metered feature. */\n create: (feature: string, input: CreateUsageAlertInput) =>\n this.#request<UsageAlert>(\n \"POST\",\n `/v1/features/${encodeURIComponent(feature)}/alerts`,\n input,\n ),\n /** Delete one alert/cap rule by id. */\n remove: (feature: string, id: string) =>\n this.#request<{ removed: boolean }>(\n \"DELETE\",\n `/v1/features/${encodeURIComponent(feature)}/alerts/${encodeURIComponent(id)}`,\n ),\n },\n };\n\n integrations = {\n /** The registered payment providers + their config manifests (drives the picker + form). */\n providers: (options?: CallOptions) =>\n this.#request<{ providers: PaymentProviderDefinition[] }>(\n \"GET\",\n \"/v1/integrations/providers\",\n undefined,\n options,\n ).then((r) => r.providers),\n /** List configured credential sets per currency (provider + non-secret config). Secret-only. */\n list: (options?: CallOptions) =>\n this.#request<{ integrations: ProviderCredentialSummary[] }>(\n \"GET\",\n \"/v1/integrations\",\n undefined,\n options,\n ).then((r) => r.integrations),\n /**\n * Verify a provider's configuration against its live API. Pass `currency` to\n * choose a credential set and `target` to probe one verify target (Paymob: a\n * method); pass `sampleToken` (SANDBOX ONLY) to run a functional sub-test.\n * This probe hits the provider's live API (mutates nothing here), so it takes a\n * per-call {@link CallOptions} for a timeout / cancellation - `opts` is spread into\n * the body, so `options` stays a separate trailing arg (never merged in).\n */\n verify: (\n provider: string,\n opts?: { currency?: string; target?: string; sampleToken?: string },\n options?: CallOptions,\n ) =>\n this.#request<ProviderVerifyResult>(\n \"POST\",\n \"/v1/integrations/verify\",\n { provider, ...opts },\n options,\n ),\n /** Manage stored payment-provider credentials (the dashboard's \"manage credentials\" forms). */\n credentials: {\n /** The editable view of stored credential sets (secrets are never returned). */\n list: (options?: CallOptions) =>\n this.#request<{ credentials: CredentialSet[] }>(\n \"GET\",\n \"/v1/integrations/credentials\",\n undefined,\n options,\n ).then((r) => r.credentials),\n /** Create or rotate a (provider, currency) credential set. */\n upsert: (input: UpsertCredentialsInput) =>\n this.#request<CredentialSet>(\"PUT\", \"/v1/integrations/credentials\", input),\n /** Delete a (provider, currency) credential set. */\n remove: (provider: string, currency: string) =>\n this.#request<{ removed: boolean }>(\n \"DELETE\",\n `/v1/integrations/credentials/${encodeURIComponent(provider)}/${encodeURIComponent(currency)}`,\n ),\n },\n };\n\n /** The merchant's business identity - the seller block on documents and email from-name. */\n businessProfile = {\n /** The stored profile, or null if one was never saved. */\n get: (options?: CallOptions) =>\n this.#request<{ profile: BusinessProfile | null }>(\n \"GET\",\n \"/v1/business-profile\",\n undefined,\n options,\n ).then((r) => r.profile),\n /** Create or update the profile; omit a field to keep it, send \"\" to clear it. */\n update: (input: UpdateBusinessProfileInput) =>\n this.#request<{ profile: BusinessProfile }>(\"PUT\", \"/v1/business-profile\", input).then(\n (r) => r.profile,\n ),\n };\n\n /**\n * Hosted customer surfaces (Phase G, ADR-0014). Mint a portal session for one of\n * your signed-in users and redirect them to the returned `url` - the self-serve\n * portal, or a `checkout` hand-off for `productId`. The Customer's own calls go\n * through {@link BillowPortal}, constructed with the session token.\n */\n /** Identity of this key's tenant - org, environment, configured currencies. */\n me(options?: CallOptions): Promise<Whoami> {\n return this.#request<Whoami>(\"GET\", \"/v1/me\", undefined, options);\n }\n\n /**\n * Global search (PRD-12) across customers, invoices, subscriptions, and charges\n * for the key's `(project, environment)`. Bounded per entity (default 5, max 10);\n * a blank query returns empty results. Backs the dashboard's ⌘K palette.\n */\n search(q: string, limit?: number, options?: CallOptions): Promise<SearchResults> {\n return this.#request<SearchResults>(\n \"GET\",\n `/v1/search${toQuery({ q, limit })}`,\n undefined,\n options,\n );\n }\n\n // ── The headline verbs ──────────────────────────────────────────────\n\n /**\n * Gate access to a feature. `featureId` is the feature slug. Returns\n * `{ allowed, balance }` - `balance` is `null` for unlimited/boolean features.\n */\n check(\n input: { customerId: string; featureId: string },\n options?: CallOptions,\n ): Promise<CheckResult> {\n return this.#request<CheckResult>(\"POST\", \"/v1/check\", input, options);\n }\n\n /**\n * Record usage of a metered feature (`value` defaults to 1; negative credits\n * back). Pass `idempotencyKey` to make a retried call a no-op - which also makes the call\n * safe to retry automatically when `maxRetries` is set.\n */\n track(\n input: {\n customerId: string;\n featureId: string;\n value?: number;\n /** Arbitrary event properties - used by meter filters and property-based aggregations. */\n properties?: Record<string, string | number>;\n idempotencyKey?: string;\n /**\n * When the usage occurred (ISO-8601). Defaults to now; backdate it to land a value in a\n * still-open reset window (e.g. a finalized month-end total). Rejected if more than 5 minutes\n * in the future or more than 7 days in the past.\n */\n occurredAt?: string;\n },\n options?: CallOptions,\n ): Promise<TrackResult> {\n return this.#request<TrackResult>(\n \"POST\",\n \"/v1/track\",\n input,\n input.idempotencyKey ? { ...options, idempotencyKey: input.idempotencyKey } : options,\n );\n }\n\n /** List a customer's live entitlements (one per feature). */\n entitlements(\n customerId: string,\n options?: CallOptions,\n ): Promise<{ entitlements: EntitlementView[] }> {\n return this.#request(\n \"GET\",\n `/v1/customers/${encodeURIComponent(customerId)}/entitlements`,\n undefined,\n options,\n );\n }\n\n /** Start a subscription → returns a hosted checkout URL to redirect the customer to. */\n attach(input: CreateSubscriptionInput, options?: RequestOptions): Promise<AttachResult> {\n return this.#request<AttachResult>(\"POST\", \"/v1/attach\", input, options);\n }\n}\n\n/**\n * The browser-safe client: construct it with a **publishable** key (`bl_<env>_pk_…`)\n * to read the public catalog from any origin (CORS-enabled). It exposes ONLY public,\n * project-scoped reads - products and their prices - so it is safe to ship in a\n * frontend bundle. Customer-scoped data (entitlements, checkout) is never reachable\n * with a publishable key; use the server handler or a portal session for that.\n */\nexport class BillowPublishable {\n #ctx: RequestContext;\n\n constructor(publishableKey: string, opts: BillowOptions = {}) {\n if (!publishableKey) throw new Error(\"billow: a publishable key is required\");\n // Guard against shipping a SECRET key in a browser bundle: publishable keys look\n // like `bl_<env>_pk_…`. The catalog accepts any key, so a misplaced secret would\n // appear to work while leaking full API access.\n if (!publishableKey.includes(\"_pk_\")) {\n throw new Error(\n \"billow: BillowPublishable needs a publishable key (bl_<env>_pk_…), not a secret key\",\n );\n }\n this.#ctx = makeContext(publishableKey, opts);\n }\n\n products = {\n /** The active catalog - products and their prices, for a pricing table. */\n list: (options?: CallOptions) =>\n apiRequest<{ products: Product[] }>(\n this.#ctx,\n \"GET\",\n \"/v1/products\",\n undefined,\n options,\n ).then((r) => r.products),\n /** Fetch one active product (with its prices) by slug. */\n get: (slug: string, options?: CallOptions) =>\n apiRequest<Product>(\n this.#ctx,\n \"GET\",\n `/v1/products/${encodeURIComponent(slug)}`,\n undefined,\n options,\n ),\n };\n}\n\n/**\n * The customer-surface client (Phase G, ADR-0014): construct it with a portal\n * session token (minted by your backend via `billow.portalSessions.create`) to act\n * as that one Customer. It exposes only their own data - no operator/secret-key\n * surface - so the token can be held by a hosted portal app safely.\n */\nexport class BillowPortal {\n #ctx: RequestContext;\n\n constructor(sessionToken: string, opts: BillowOptions = {}) {\n if (!sessionToken) throw new Error(\"billow: a portal session token is required\");\n this.#ctx = makeContext(sessionToken, opts);\n }\n\n /** The portal shell: flow, return URL, merchant brand, and customer identity -\n * the lightweight payload the hosting app frames every page with, and the\n * validate-and-route check at login. */\n session(options?: CallOptions): Promise<PortalSessionInfo> {\n return apiRequest<PortalSessionInfo>(this.#ctx, \"GET\", \"/portal/session\", undefined, options);\n }\n\n /**\n * The single-use entry exchange (ADR-0039): on a client built with a minted link's entry\n * token, trade it for the session token to use from now on. Succeeds once per link, and\n * afterwards only for the browser presenting that exchange's `sessionToken`; anything else is\n * a 401. For a hosted surface; an integrator's own surface may keep using the entry token.\n */\n exchange(\n input: PortalSessionExchangeInput,\n options?: CallOptions,\n ): Promise<PortalSessionExchange> {\n return apiRequest<PortalSessionExchange>(\n this.#ctx,\n \"POST\",\n \"/portal/session/exchange\",\n input,\n options,\n );\n }\n\n /** The self-serve home payload: identity + subscriptions + usage + saved cards. */\n me(options?: CallOptions): Promise<PortalView> {\n return apiRequest<PortalView>(this.#ctx, \"GET\", \"/portal/me\", undefined, options);\n }\n\n invoices = {\n /** The Customer's invoices (newest first). Auto-paginating. */\n list: (\n params: ListParams & { status?: InvoiceListItem[\"status\"] } = {},\n options?: CallOptions,\n ): ListPromise<InvoiceListItem> =>\n makeListPromise(\n (p) =>\n apiRequest<Paginated<InvoiceListItem>>(\n this.#ctx,\n \"GET\",\n `/portal/invoices${toQuery(p)}`,\n undefined,\n options,\n ),\n params,\n ),\n /** One of the Customer's invoices, in the list's shape. */\n get: (id: string, options?: CallOptions) =>\n apiRequest<InvoiceListItem>(\n this.#ctx,\n \"GET\",\n `/portal/invoices/${encodeURIComponent(id)}`,\n undefined,\n options,\n ),\n /** Open or resume checkout for this customer's outstanding invoice. */\n pay: (id: string, input: PayInvoiceInput = {}, options?: CallOptions) =>\n apiRequest<InvoicePaymentResult>(\n this.#ctx,\n \"POST\",\n `/portal/invoices/${encodeURIComponent(id)}/pay`,\n input,\n options,\n ),\n /** Download one of the Customer's invoices as a PDF (raw bytes). */\n pdf: (id: string, options?: CallOptions) =>\n apiRequestBinary(this.#ctx, \"GET\", `/portal/invoices/${encodeURIComponent(id)}/pdf`, options),\n /** Download the payment receipt for one of the Customer's invoices (raw bytes). */\n receiptPdf: (id: string, options?: CallOptions) =>\n apiRequestBinary(\n this.#ctx,\n \"GET\",\n `/portal/invoices/${encodeURIComponent(id)}/receipt.pdf`,\n options,\n ),\n };\n\n subscriptions = {\n /** Schedule one of the Customer's subscriptions to cancel at period end. */\n cancel: (id: string) =>\n apiRequest<PortalSubscriptionStatus>(\n this.#ctx,\n \"POST\",\n `/portal/subscriptions/${encodeURIComponent(id)}/cancel`,\n ),\n /** Reverse a scheduled period-end cancellation. */\n resumeCancellation: (id: string) =>\n apiRequest<PortalSubscriptionStatus>(\n this.#ctx,\n \"POST\",\n `/portal/subscriptions/${encodeURIComponent(id)}/resume-cancellation`,\n ),\n /** Move one of the Customer's subscriptions to another plan (upgrade now / downgrade scheduled). */\n changePlan: (id: string, productId: string) =>\n apiRequest<PortalPlanChange>(\n this.#ctx,\n \"POST\",\n `/portal/subscriptions/${encodeURIComponent(id)}/plan`,\n { productId },\n ),\n /** Clear a pending downgrade. */\n clearScheduledPlanChange: (id: string) =>\n apiRequest<PortalSubscriptionStatus>(\n this.#ctx,\n \"DELETE\",\n `/portal/subscriptions/${encodeURIComponent(id)}/plan`,\n ),\n };\n\n paymentMethod = {\n /** Open a checkout to save or replace the Customer's card (zero-amount tokenization). */\n update: (returnUrl?: string) =>\n apiRequest<PortalCardSetupResult>(\n this.#ctx,\n \"POST\",\n \"/portal/payment-method/update\",\n returnUrl ? { returnUrl } : {},\n ),\n };\n\n paymentMethods = {\n list: (options?: CallOptions) =>\n apiRequest<{ paymentMethods: SavedPaymentMethod[] }>(\n this.#ctx,\n \"GET\",\n \"/portal/payment-methods\",\n undefined,\n options,\n ).then((r) => r.paymentMethods),\n setDefault: (id: string) =>\n apiRequest<SavedPaymentMethod>(\n this.#ctx,\n \"POST\",\n `/portal/payment-methods/${encodeURIComponent(id)}/default`,\n ),\n remove: (id: string) =>\n apiRequest<{ removed: boolean }>(\n this.#ctx,\n \"DELETE\",\n `/portal/payment-methods/${encodeURIComponent(id)}`,\n ),\n };\n\n /** The checkout hand-off, for a session minted with `flow: \"checkout\"`. */\n checkout = {\n /** The plan being confirmed (product + base price). */\n get: (options?: CallOptions) =>\n apiRequest<PortalCheckout>(this.#ctx, \"GET\", \"/portal/checkout\", undefined, options),\n /** Subscribe to the plan → returns the hosted Paymob checkout URL to redirect to. */\n start: (input: PortalCheckoutStartInput = {}) =>\n apiRequest<{ checkoutUrl: string | null }>(this.#ctx, \"POST\", \"/portal/checkout\", input),\n };\n}\n","\"use client\";\n\n/**\n * React adapter (ARCHITECTURE §6: \"framework adapters - React hooks first\"). These\n * hooks talk to the same-origin billow handler (`@usebillow/sdk/server`), never to billow\n * directly - the secret key stays on your server and the customer is resolved from\n * your session. Wrap your app in `<BillowProvider>` and use the hooks below.\n *\n * <BillowProvider>\n * <App />\n * </BillowProvider>\n *\n * const { allowed } = useCheck(\"projects\");\n * const attach = useAttach();\n * <button onClick={() => attach({ productId: \"pro\" }, { redirect: true })}>Upgrade</button>\n */\nimport {\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport type { AttachResult, CheckResult, EntitlementView, Product, TrackResult } from \"./index\";\nimport { BillowPublishable } from \"./index\";\n\ninterface BillowContextValue {\n request: <T>(method: string, path: string, body?: unknown) => Promise<T>;\n entitlements: EntitlementView[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n}\n\nconst BillowContext = createContext<BillowContextValue | null>(null);\n\nexport interface BillowProviderProps {\n children: ReactNode;\n /** Where the billow handler is mounted. Default `/api/billow`. */\n basePath?: string;\n /** Custom fetch (SSR/testing). Defaults to the global `fetch`. */\n fetcher?: typeof fetch;\n}\n\nexport function BillowProvider({\n children,\n basePath = \"/api/billow\",\n fetcher,\n}: BillowProviderProps) {\n const base = basePath.replace(/\\/$/, \"\");\n const doFetch = fetcher ?? (globalThis.fetch as typeof fetch);\n\n const request = useCallback(\n async <T,>(method: string, path: string, body?: unknown): Promise<T> => {\n const init: RequestInit = { method };\n if (body) {\n init.headers = { \"content-type\": \"application/json\" };\n init.body = JSON.stringify(body);\n }\n const res = await doFetch(`${base}${path}`, init);\n const data = (await res.json().catch(() => ({}))) as { error?: { message?: string } };\n if (!res.ok) {\n throw new Error(data.error?.message ?? `billow ${method} ${path} failed (${res.status})`);\n }\n return data as T;\n },\n [base, doFetch],\n );\n\n const [entitlements, setEntitlements] = useState<EntitlementView[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const refresh = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const data = await request<{ entitlements: EntitlementView[] }>(\"GET\", \"/entitlements\");\n setEntitlements(data.entitlements);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [request]);\n\n useEffect(() => {\n void refresh();\n }, [refresh]);\n\n const value = useMemo<BillowContextValue>(\n () => ({ request, entitlements, isLoading, error, refresh }),\n [request, entitlements, isLoading, error, refresh],\n );\n\n return <BillowContext.Provider value={value}>{children}</BillowContext.Provider>;\n}\n\nfunction useBillowContext(): BillowContextValue {\n const ctx = useContext(BillowContext);\n if (!ctx) throw new Error(\"billow: wrap your app in <BillowProvider>\");\n return ctx;\n}\n\n/** The identified customer's live entitlements (one per granted feature), with a `refresh`. */\nexport function useEntitlements(): {\n entitlements: EntitlementView[];\n isLoading: boolean;\n error: Error | null;\n refresh: () => Promise<void>;\n} {\n const { entitlements, isLoading, error, refresh } = useBillowContext();\n return { entitlements, isLoading, error, refresh };\n}\n\n/**\n * Gate a feature, derived client-side from the cached entitlements (no round-trip).\n * `error` is set when the entitlements fetch failed - distinguish that from a real\n * `allowed: false` (otherwise an auth/network failure looks like a denied feature).\n */\nexport function useCheck(\n featureId: string,\n): CheckResult & { isLoading: boolean; error: Error | null } {\n const { entitlements, isLoading, error } = useBillowContext();\n const match = entitlements.find((e) => e.feature === featureId);\n return {\n feature: featureId,\n allowed: match?.allowed ?? false,\n balance: match?.balance ?? null,\n isLoading,\n error,\n };\n}\n\n/** Start a subscription → checkout URL. Pass `{ redirect: true }` to navigate to it. */\nexport function useAttach(): (\n input: { productId: string; couponCode?: string },\n opts?: { redirect?: boolean },\n) => Promise<AttachResult> {\n const { request, refresh } = useBillowContext();\n return useCallback(\n async (input, opts = {}) => {\n const result = await request<AttachResult>(\"POST\", \"/attach\", input);\n await refresh();\n const loc = (globalThis as { location?: { href: string } }).location;\n if (opts.redirect && result.url && loc) loc.href = result.url;\n return result;\n },\n [request, refresh],\n );\n}\n\n/**\n * Record usage from the browser. Requires the handler's `allowClientTrack` - most\n * apps should `track` server-side instead (a browser caller can forge usage).\n */\nexport function useTrack(): (input: {\n featureId: string;\n value?: number;\n idempotencyKey?: string;\n}) => Promise<TrackResult> {\n const { request, refresh } = useBillowContext();\n return useCallback(\n async (input) => {\n const result = await request<TrackResult>(\"POST\", \"/track\", input);\n await refresh();\n return result;\n },\n [request, refresh],\n );\n}\n\n/** Fetch the public product catalog (for a pricing table). */\nexport function useProducts(): { products: Product[]; isLoading: boolean; error: Error | null } {\n const { request } = useBillowContext();\n const [products, setProducts] = useState<Product[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n let active = true;\n setIsLoading(true);\n request<{ products: Product[] }>(\"GET\", \"/products\")\n .then((r) => {\n if (!active) return;\n setProducts(r.products);\n setError(null);\n })\n .catch((err) => {\n if (active) setError(err instanceof Error ? err : new Error(String(err)));\n })\n .finally(() => {\n if (active) setIsLoading(false);\n });\n return () => {\n active = false;\n };\n }, [request]);\n\n return { products, isLoading, error };\n}\n\nexport interface PublishableConfig {\n /** A publishable key (`bl_<env>_pk_…`) - safe to ship in a frontend bundle. */\n publishableKey: string;\n /** The Billow API base URL. Defaults to the hosted API, `https://api.usebillow.com`. */\n baseUrl?: string;\n}\n\n/**\n * Drop-in catalog reader for a pure-SPA pricing table: fetches the public products +\n * prices directly from the browser with a publishable key (CORS-enabled), no backend\n * handler required. Reads only public data - customer entitlements/checkout still need\n * the server handler or a portal session.\n */\nexport function usePublishableProducts(config: PublishableConfig): {\n products: Product[];\n isLoading: boolean;\n error: string | null;\n} {\n const { publishableKey, baseUrl } = config;\n const [products, setProducts] = useState<Product[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let active = true;\n setIsLoading(true);\n const client = new BillowPublishable(publishableKey, baseUrl ? { baseUrl } : {});\n client.products.list().then(\n (list) => {\n if (!active) return;\n setProducts(list);\n setError(null);\n setIsLoading(false);\n },\n (e: unknown) => {\n if (!active) return;\n setError(e instanceof Error ? e.message : String(e));\n setIsLoading(false);\n },\n );\n return () => {\n active = false;\n };\n }, [publishableKey, baseUrl]);\n\n return { products, isLoading, error };\n}\n"]}
package/dist/react.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { A as AttachResult, b as CheckResult, E as EntitlementView, P as Product, T as TrackResult } from './billing-8ug1Gw05.cjs';
4
- import './index.d-DSEYhV2c.cjs';
3
+ import { A as AttachResult, b as CheckResult, E as EntitlementView, P as Product, T as TrackResult } from './billing-CDEO8Jqv.cjs';
4
+ import './index.d-D5v2ETKF.cjs';
5
5
  import 'zod';
6
6
  import './billing-status-DZkB0VPK.cjs';
7
7
 
package/dist/react.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { A as AttachResult, b as CheckResult, E as EntitlementView, P as Product, T as TrackResult } from './billing-Bs10EcjR.js';
4
- import './index.d-DSEYhV2c.js';
3
+ import { A as AttachResult, b as CheckResult, E as EntitlementView, P as Product, T as TrackResult } from './billing-BEbVLGe9.js';
4
+ import './index.d-D5v2ETKF.js';
5
5
  import 'zod';
6
6
  import './billing-status-DZkB0VPK.js';
7
7
 
package/dist/react.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { BillowPublishable } from './chunk-XUFQG6EA.js';
2
+ import { BillowPublishable } from './chunk-XRZZUXOR.js';
3
3
  import './chunk-47J7FYM5.js';
4
4
  import { createContext, useCallback, useState, useEffect, useMemo, useContext } from 'react';
5
5
  import { jsx } from 'react/jsx-runtime';
package/dist/server.d.cts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Billow } from './index.cjs';
2
- import './index.d-DSEYhV2c.cjs';
2
+ import './index.d-D5v2ETKF.cjs';
3
3
  import 'zod';
4
4
  import './billing-status-DZkB0VPK.cjs';
5
5
  import './status.cjs';
6
- import './billing-8ug1Gw05.cjs';
7
- import './hosted-domains-zWm2srOk.cjs';
6
+ import './billing-CDEO8Jqv.cjs';
7
+ import './hosted-domains-Cuk3j04R.cjs';
8
8
 
9
9
  /**
10
10
  * Backend handler for the browser-facing surface (the React hooks talk to this,
package/dist/server.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { Billow } from './index.js';
2
- import './index.d-DSEYhV2c.js';
2
+ import './index.d-D5v2ETKF.js';
3
3
  import 'zod';
4
4
  import './billing-status-DZkB0VPK.js';
5
5
  import './status.js';
6
- import './billing-Bs10EcjR.js';
7
- import './hosted-domains-BrnwkHqq.js';
6
+ import './billing-BEbVLGe9.js';
7
+ import './hosted-domains-DMgokjiv.js';
8
8
 
9
9
  /**
10
10
  * Backend handler for the browser-facing surface (the React hooks talk to this,
package/dist/webhooks.cjs CHANGED
@@ -5,6 +5,7 @@ var WEBHOOK_EVENT_TYPES = [
5
5
  "charge.succeeded",
6
6
  "charge.failed",
7
7
  "charge.refunded",
8
+ "charge.refund_reversed",
8
9
  "charge.disputed",
9
10
  "charge.dispute_resolved",
10
11
  "invoice.paid",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/webhook-event-types.ts","../src/webhooks.ts"],"names":[],"mappings":";;;AASO,IAAM,mBAAA,GAAsB;AAAA,EACjC,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA,yBAAA;AAAA,EACA,cAAA;AAAA,EACA,wBAAA;AAAA,EACA,4BAAA;AAAA,EACA,wBAAA;AAAA,EACA,gCAAA;AAAA,EACA,sBAAA;AAAA,EACA,uBAAA;AAAA,EACA,6BAAA;AAAA,EACA,sCAAA;AAAA,EACA,qBAAA;AAAA,EACA,qBAAA;AAAA,EACA,sBAAA;AAAA,EACA,sBAAA;AAAA,EACA,+BAAA;AAAA,EACA,6BAAA;AAAA,EACA,kCAAA;AAAA,EACA,iCAAA;AAAA,EACA,uBAAA;AAAA,EACA,oBAAA;AAAA,EACA,qBAAA;AAAA,EACA,mBAAA;AAAA,EACA,yBAAA;AAAA,EACA,aAAA;AAAA,EACA,mBAAA;AAAA,EACA,kCAAA;AAAA,EACA,sBAAA;AAAA,EACA,sBAAA;AAAA,EACA,4BAAA;AAAA,EACA,yBAAA;AAAA,EACA,sBAAA;AAAA,EACA,wBAAA;AAAA,EACA,2BAAA;AAAA,EACA,yBAAA;AAAA,EACA,2BAAA;AAAA,EACA,oCAAA;AAAA,EACA;AACF;AAqBA,SAAS,qBACP,MAAA,EACsD;AACtD,EAAA,OAAO,mBAAA,CAAoB,MAAA;AAAA,IAAO,CAAC,IAAA,KACjC,IAAA,CAAK,UAAA,CAAW,MAAM;AAAA,GACxB;AACF;AAGO,IAAM,kBAAA,GAAiD,qBAAqB,SAAS;AAErF,IAAM,mBAAA,GAAmD,qBAAqB,UAAU;AAMxF,IAAM,wBAAA,GACX,qBAAqB,eAAe;AAE/B,IAAM,uBAAA,GACX,qBAAqB,cAAc;AAE9B,IAAM,iBAAA,GAA+C,qBAAqB,QAAQ;AAElF,IAAM,kBAAA,GAAiD,qBAAqB,SAAS;AAErF,IAAM,yBAAA,GACX,qBAAqB,gBAAgB;AAGhC,SAAS,oBAAoB,IAAA,EAA6C;AAC/E,EAAA,OAAO,IAAA,CAAK,WAAW,eAAe,CAAA;AACxC;;;ACqNO,IAAM,wBAAA,GAAN,cAAuC,KAAA,CAAM;AAAA,EAClD,YAAY,OAAA,EAAiB;AAC3B,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,0BAAA;AAAA,EACd;AACF;AAEA,IAAM,yBAAA,GAA4B,GAAA;AAClC,IAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAQhC,SAAS,YAAY,MAAA,EAAqC;AACxD,EAAA,IAAI,YAAY,MAAA,CAAO,GAAA;AACvB,EAAA,MAAM,aAAuB,EAAC;AAC9B,EAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,EAAG;AACpC,IAAA,MAAM,EAAA,GAAK,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAC3B,IAAA,IAAI,OAAO,EAAA,EAAI;AACf,IAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,EAAE,EAAE,IAAA,EAAK;AACnC,IAAA,MAAM,QAAQ,IAAA,CAAK,KAAA,CAAM,EAAA,GAAK,CAAC,EAAE,IAAA,EAAK;AACtC,IAAA,IAAI,GAAA,KAAQ,GAAA,EAAK,SAAA,GAAY,MAAA,CAAO,KAAK,CAAA;AAAA,SAAA,IAChC,GAAA,KAAQ,IAAA,EAAM,UAAA,CAAW,IAAA,CAAK,KAAK,CAAA;AAAA,EAC9C;AACA,EAAA,IAAI,CAAC,OAAO,QAAA,CAAS,SAAS,KAAK,UAAA,CAAW,MAAA,KAAW,GAAG,OAAO,IAAA;AACnE,EAAA,OAAO,EAAE,WAAW,UAAA,EAAW;AACjC;AAEA,eAAe,OAAA,CAAQ,QAAgB,OAAA,EAAkC;AACvE,EAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,MAAA,CAAO,SAAA;AAAA,IAC9B,KAAA;AAAA,IACA,OAAA,CAAQ,OAAO,MAAM,CAAA;AAAA,IACrB,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,SAAA,EAAU;AAAA,IAChC,KAAA;AAAA,IACA,CAAC,MAAM;AAAA,GACT;AACA,EAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,MAAA,CAAO,IAAA,CAAK,QAAQ,GAAA,EAAK,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAC,CAAA;AACzE,EAAA,OAAO,MAAM,IAAA,CAAK,IAAI,WAAW,GAAG,CAAA,EAAG,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,CAAS,EAAE,EAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AACxF;AAGA,SAAS,eAAA,CAAgB,GAAW,CAAA,EAAoB;AACtD,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,QAAA,IAAY,CAAA,CAAE,UAAA,CAAW,CAAC,CAAA,GAAI,CAAA,CAAE,WAAW,CAAC,CAAA;AAC/E,EAAA,OAAO,QAAA,KAAa,CAAA;AACtB;AAOA,eAAsB,gBACpB,OAAA,EACA,MAAA,EACA,MAAA,EACA,IAAA,GAAsB,EAAC,EACL;AAClB,EAAA,IAAI,CAAC,QAAQ,OAAO,KAAA;AACpB,EAAA,MAAM,MAAA,GAAS,YAAY,MAAM,CAAA;AACjC,EAAA,IAAI,CAAC,QAAQ,OAAO,KAAA;AAEpB,EAAA,MAAM,SAAA,GAAY,KAAK,gBAAA,IAAoB,yBAAA;AAC3C,EAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AACxC,EAAA,IAAI,KAAK,GAAA,CAAI,GAAA,GAAM,OAAO,SAAS,CAAA,GAAI,WAAW,OAAO,KAAA;AAEzD,EAAA,MAAM,QAAA,GAAW,MAAM,OAAA,CAAQ,MAAA,EAAQ,GAAG,MAAA,CAAO,SAAS,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE,CAAA;AACvE,EAAA,OAAO,MAAA,CAAO,WAAW,IAAA,CAAK,CAAC,QAAQ,eAAA,CAAgB,GAAA,EAAK,QAAQ,CAAC,CAAA;AACvE;AAYA,eAAsB,WAAA,CACpB,OAAA,EACA,MAAA,EACA,IAAA,GAA+B,EAAC,EACf;AACjB,EAAA,MAAM,SAAA,GAAY,KAAK,SAAA,IAAa,IAAA,CAAK,MAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AAChE,EAAA,MAAM,SAAA,GAAY,MAAM,OAAA,CAAQ,MAAA,EAAQ,GAAG,SAAS,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE,CAAA;AACjE,EAAA,OAAO,CAAA,EAAA,EAAK,SAAS,CAAA,IAAA,EAAO,SAAS,CAAA,CAAA;AACvC;AASA,eAAsB,eACpB,OAAA,EACA,MAAA,EACA,MAAA,EACA,IAAA,GAAsB,EAAC,EACA;AACvB,EAAA,IAAI,CAAE,MAAM,eAAA,CAAgB,SAAS,MAAA,EAAQ,MAAA,EAAQ,IAAI,CAAA,EAAI;AAC3D,IAAA,MAAM,IAAI,yBAAyB,8CAA8C,CAAA;AAAA,EACnF;AACA,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI;AACF,IAAA,MAAA,GAAS,IAAA,CAAK,MAAM,OAAO,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,yBAAyB,0CAA0C,CAAA;AAAA,EAC/E;AACA,EAAA,IAAI,CAAC,cAAA,CAAe,MAAM,CAAA,EAAG;AAC3B,IAAA,MAAM,IAAI,yBAAyB,sDAAsD,CAAA;AAAA,EAC3F;AACA,EAAA,OAAO,MAAA;AACT;AAsCO,SAAS,wBAAwB,MAAA,EAImD;AACzF,EAAA,MAAM,EAAE,MAAA,EAAQ,QAAA,EAAU,gBAAA,EAAiB,GAAI,MAAA;AAC/C,EAAA,MAAM,IAAA,GAAO,gBAAA,KAAqB,MAAA,GAAY,MAAA,GAAY,EAAE,gBAAA,EAAiB;AAC7E,EAAA,OAAO,OAAO,SAAS,eAAA,KAAoB;AACzC,IAAA,MAAM,QAAQ,MAAM,cAAA,CAAe,OAAA,EAAS,eAAA,EAAiB,QAAQ,IAAI,CAAA;AAUzE,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,MAAA,CAAO,QAAA,EAAU,KAAA,CAAM,IAAI,CAAA,GACzC,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,GACpB,MAAA;AACJ,IAAA,MAAM,OAAA,GAAU,OAAO,QAAA,CAAS,OAAA;AAChC,IAAA,IAAI,OAAA,EAAS,MAAM,OAAA,CAAQ,KAAK,CAAA;AAChC,IAAA,OAAO,KAAA;AAAA,EACT,CAAA;AACF;AAEA,SAAS,eAAe,KAAA,EAAuC;AAC7D,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,MAAM,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AAOV,EAAA,OACE,OAAO,EAAE,EAAA,KAAO,QAAA,IAChB,OAAO,CAAA,CAAE,OAAA,KAAY,YACrB,OAAO,CAAA,CAAE,SAAS,QAAA,IAClB,OAAO,EAAE,SAAA,KAAc,QAAA,IACvB,OAAO,CAAA,CAAE,IAAA,KAAS,QAAA,IAClB,CAAA,CAAE,IAAA,KAAS,IAAA;AAEf","file":"webhooks.cjs","sourcesContent":["/**\n * The webhook event catalog: every event type billow emits, and its families as types and runtime\n * arrays (for subscription pickers and exhaustive routing). Re-exported by ./webhooks.ts.\n */\n\n/**\n * Every event type billow emits, in contract order. Mirrors `@billow/core`'s\n * EVENT_TYPES - exported as a runtime array so UIs can render a subscription picker.\n */\nexport const WEBHOOK_EVENT_TYPES = [\n \"charge.succeeded\",\n \"charge.failed\",\n \"charge.refunded\",\n \"charge.disputed\",\n \"charge.dispute_resolved\",\n \"invoice.paid\",\n \"invoice.payment_failed\",\n \"subscription.trial_started\",\n \"subscription.activated\",\n \"subscription.activation_failed\",\n \"subscription.renewed\",\n \"subscription.past_due\",\n \"subscription.payment_failed\",\n \"subscription.requires_payment_method\",\n \"subscription.unpaid\",\n \"subscription.paused\",\n \"subscription.resumed\",\n \"subscription.updated\",\n \"subscription.cancel_scheduled\",\n \"subscription.cancel_resumed\",\n \"subscription.downgrade_scheduled\",\n \"subscription.downgrade_canceled\",\n \"subscription.canceled\",\n \"subscription.ended\",\n \"entitlement.granted\",\n \"entitlement.reset\",\n \"usage.threshold_reached\",\n \"usage.alert\",\n \"usage.cap_reached\",\n \"credit_balance.threshold_crossed\",\n \"credit_grant.created\",\n \"credit_grant.expired\",\n \"credit_reservation.expired\",\n \"credit_top_up.succeeded\",\n \"credit_top_up.failed\",\n \"credit_top_up.refunded\",\n \"credit_top_up.unfulfilled\",\n \"hosted_domain.activated\",\n \"hosted_domain.dns_failing\",\n \"hosted_domain.reassignment_pending\",\n \"hosted_domain.disabled\",\n] as const;\n\n/** A valid billow event type (the compile-time freeze derived from the array above). */\nexport type BillowEventType = (typeof WEBHOOK_EVENT_TYPES)[number];\n\n/** The `charge.*` subset of {@link BillowEventType}. */\nexport type ChargeEventType = Extract<BillowEventType, `charge.${string}`>;\n/** The `invoice.*` subset of {@link BillowEventType}. */\nexport type InvoiceEventType = Extract<BillowEventType, `invoice.${string}`>;\n/** The `subscription.*` subset of {@link BillowEventType} - the events that change a subscription. */\nexport type SubscriptionEventType = Extract<BillowEventType, `subscription.${string}`>;\n/** The `entitlement.*` subset of {@link BillowEventType}. */\nexport type EntitlementEventType = Extract<BillowEventType, `entitlement.${string}`>;\n/** The `usage.*` subset of {@link BillowEventType}. */\nexport type UsageEventType = Extract<BillowEventType, `usage.${string}`>;\n/** The prepaid credits subset of {@link BillowEventType} (`credit_balance.*`, `credit_grant.*`, ...). */\nexport type CreditEventType = Extract<BillowEventType, `credit_${string}`>;\n/** The `hosted_domain.*` subset of {@link BillowEventType}: custom portal domains. */\nexport type HostedDomainEventType = Extract<BillowEventType, `hosted_domain.${string}`>;\n\n/** The subset of {@link WEBHOOK_EVENT_TYPES} sharing `prefix`, as a runtime array (order preserved). */\nfunction eventTypesWithPrefix<P extends string>(\n prefix: P,\n): readonly Extract<BillowEventType, `${P}${string}`>[] {\n return WEBHOOK_EVENT_TYPES.filter((type): type is Extract<BillowEventType, `${P}${string}`> =>\n type.startsWith(prefix),\n );\n}\n\n/** The `charge.*` event types, as a runtime array (for pickers / exhaustive routing). */\nexport const CHARGE_EVENT_TYPES: readonly ChargeEventType[] = eventTypesWithPrefix(\"charge.\");\n/** The `invoice.*` event types, as a runtime array. */\nexport const INVOICE_EVENT_TYPES: readonly InvoiceEventType[] = eventTypesWithPrefix(\"invoice.\");\n/**\n * The subscription lifecycle event types - the `subscription.*` subset of\n * {@link WEBHOOK_EVENT_TYPES}, as a runtime array. Use it (or {@link isSubscriptionEvent})\n * to select the events that affect a subscription mirror without a brittle string match.\n */\nexport const SUBSCRIPTION_EVENT_TYPES: readonly SubscriptionEventType[] =\n eventTypesWithPrefix(\"subscription.\");\n/** The `entitlement.*` event types, as a runtime array. */\nexport const ENTITLEMENT_EVENT_TYPES: readonly EntitlementEventType[] =\n eventTypesWithPrefix(\"entitlement.\");\n/** The `usage.*` event types, as a runtime array. */\nexport const USAGE_EVENT_TYPES: readonly UsageEventType[] = eventTypesWithPrefix(\"usage.\");\n/** The prepaid credits event types, as a runtime array. */\nexport const CREDIT_EVENT_TYPES: readonly CreditEventType[] = eventTypesWithPrefix(\"credit_\");\n/** The `hosted_domain.*` event types, as a runtime array. */\nexport const HOSTED_DOMAIN_EVENT_TYPES: readonly HostedDomainEventType[] =\n eventTypesWithPrefix(\"hosted_domain.\");\n\n/** Whether `type` is a subscription lifecycle event (`subscription.*`); narrows the type. */\nexport function isSubscriptionEvent(type: string): type is SubscriptionEventType {\n return type.startsWith(\"subscription.\");\n}\n","/**\n * Verify and parse billow webhook deliveries.\n *\n * billow signs each delivery with HMAC-SHA256 over `\"<timestamp>.<rawBody>\"` and\n * sends it in the `billow-signature` header as `t=<unix-seconds>,v1=<hex>`. The\n * timestamp lets you reject replays outside a freshness window.\n *\n * Verification is async and uses the Web Crypto API (`globalThis.crypto.subtle`),\n * so the same code runs in Node 20+, edge runtimes, Cloudflare Workers, Deno, and\n * Bun - it never imports `node:crypto`.\n *\n * import { constructEvent } from \"@usebillow/sdk/webhooks\";\n *\n * const event = await constructEvent(rawBody, req.headers[\"billow-signature\"], secret);\n * switch (event.type) {\n * case \"invoice.paid\": // …\n * }\n */\n\nimport type { CreditGrantKind, CreditThresholdLevel } from \"./types/credits\";\nimport type {\n HostedDomainActivatedData,\n HostedDomainDisabledData,\n HostedDomainDnsFailingData,\n HostedDomainReassignmentPendingData,\n} from \"./types/hosted-domains\";\nimport type {\n BillowEventType,\n EntitlementEventType,\n InvoiceEventType,\n SubscriptionEventType,\n} from \"./webhook-event-types\";\n\nexport * from \"./webhook-event-types\";\n\n/**\n * The fields every webhook envelope carries. Each {@link WebhookEvent} variant intersects\n * this with its own `type` + payload-typed `data`.\n */\nexport interface WebhookEventEnvelope {\n /** Stable event id - your idempotency key (delivery is at-least-once). */\n id: string;\n /** Envelope schema version. */\n version: number;\n /** ISO-8601 timestamp of when the event was recorded. */\n createdAt: string;\n}\n\n// --- Per-event-group payloads -------------------------------------------------------------\n// Shapes mirror what the server emits (packages/server/src/services: charges.ts, billing.ts,\n// renewal/*, subscriptions/*, entitlements/*). Grouped where a group's emitters are uniform;\n// `usage.*` is split per event because its emitters diverge. `customerExternalId` is added to\n// every customer-scoped payload by the server's outbox emitter (events.ts).\n\n/** `charge.succeeded` / `charge.failed`. */\nexport interface ChargeEventData {\n chargeId: string;\n /**\n * `false` on a `charge.failed` that closed a checkout its customer abandoned long before: no\n * \"payment failed\" email was sent to the customer. Absent otherwise.\n */\n notifyCustomer?: false;\n}\n/** `charge.refunded`, emitted once for each succeeded refund transition. */\nexport interface ChargeRefundedEventData {\n chargeId: string;\n refundId: string;\n amount: number;\n cumulativeAmount: number;\n currency: string;\n}\n/** Stable dispute facts shared by the opened and resolved events. */\nexport interface ChargeDisputeEventData {\n chargeId: string;\n disputeId: string;\n amount: number;\n currency: string;\n reason: string | null;\n state: \"open\" | \"won\" | \"lost\";\n}\n/** `invoice.paid` / `invoice.payment_failed`. */\nexport interface InvoiceEventData {\n invoiceId: string;\n chargeId: string;\n /**\n * `false` on an `invoice.payment_failed` that closed a checkout its customer abandoned long\n * before: no \"payment failed\" email was sent to the customer. Absent otherwise.\n */\n notifyCustomer?: false;\n}\n/** Any `subscription.*` lifecycle event. */\nexport interface SubscriptionEventData {\n subscriptionId: string;\n customerId: string;\n customerExternalId: string;\n /** The subscription's plan - so a consumer knows which product the event is about. */\n productId: string;\n productSlug: string;\n /** The product's metadata map (Stripe-style); `{}` when the plan carries none. */\n productMetadata: Record<string, string>;\n /** Set on dunning events that reference the unpaid invoice (`unpaid`, `requires_payment_method`). */\n invoiceId?: string;\n /** Set on lifecycle-ending events (`ended`, downgrade-driven `updated`). */\n reason?: string;\n /** Present when a downgrade is scheduled; null when that schedule is cleared. */\n scheduledChange?: {\n productVersionId: string;\n productId: string;\n productSlug: string;\n productName: string;\n effectiveAt: string | null;\n } | null;\n}\n/** `entitlement.granted` / `entitlement.reset`. */\nexport interface EntitlementEventData {\n subscriptionId: string;\n customerId: string;\n customerExternalId: string;\n featureId: string;\n /** Remaining allowance; `null` when the feature is unlimited. */\n balance: number | null;\n /** Whether the feature grants unlimited usage. */\n unlimited: boolean;\n}\n/** Fields shared by every `usage.*` event. */\nexport interface UsageEventBase {\n subscriptionId: string;\n customerId: string;\n customerExternalId: string;\n /** Feature slug. */\n feature: string;\n featureName: string;\n /** Aggregate usage after this step. */\n used: number;\n /** Included allowance; `null` when unlimited. */\n allowance: number | null;\n /** Remaining allowance; `null` when unlimited. */\n balance: number | null;\n}\n/** `usage.threshold_reached` - a built-in usage threshold was crossed. */\nexport interface UsageThresholdReachedData extends UsageEventBase {\n featureId: string;\n /** The threshold crossed (currently only the included allowance being exhausted). */\n level: \"exhausted\";\n}\n/** `usage.alert` - a configured usage alert fired. */\nexport interface UsageAlertData extends UsageEventBase {\n metric: string;\n threshold: number;\n recurring: boolean;\n notifyCustomer: boolean;\n}\n/** `usage.cap_reached` - a configured spend/usage cap was reached. */\nexport interface UsageCapReachedData extends UsageEventBase {\n metric: string;\n cap: number;\n capAt: number;\n}\n\n// Prepaid credits: every credit quantity is a decimal string of microcredits, never a number.\n// Payloads carry ids, codes and quantities, never a grant's, reservation's or top-up's reason or\n// metadata.\n\n/** Fields shared by every prepaid credits event. */\nexport interface CreditEventBase {\n customerId: string;\n customerExternalId: string;\n}\n/**\n * `credit_balance.threshold_crossed` - the balance moved to another level, in either direction\n * (raise a low-balance banner, or clear it). `exhausted` when nothing is spendable; otherwise\n * `critical` / `low` at or below the percentages of `thresholds.reference`, else `normal`.\n */\nexport interface CreditThresholdCrossedData extends CreditEventBase {\n level: CreditThresholdLevel;\n previousLevel: CreditThresholdLevel;\n /** The balance the level was judged on, as of `asOf`; re-read the balance for truth. */\n balance: { spendable: string; held: string; asOf: string };\n thresholds: { lowPercent: number; criticalPercent: number; reference: string | null };\n}\n/** `credit_grant.created` - credits were granted. */\nexport interface CreditGrantCreatedData extends CreditEventBase {\n grantId: string;\n kind: CreditGrantKind;\n amount: string;\n /** For an `included` grant, its window's end. */\n expiresAt: string | null;\n eligibility: string[] | null;\n topUpId: string | null;\n /** An `included` grant's subscription; null for every other kind. */\n subscriptionId: string | null;\n /** The billing period an `included` grant's window belongs to; null for every other kind. */\n periodStart: string | null;\n periodEnd: string | null;\n}\n/**\n * `credit_grant.expired` - one expiry took `expired` credits off a grant: its unheld credits when\n * it expired (or when an early end of its included window brought its expiry forward), or a held\n * part that came back to it afterwards (one more event each).\n */\nexport interface CreditGrantExpiredData extends CreditEventBase {\n grantId: string;\n kind: CreditGrantKind;\n expiresAt: string | null;\n expired: string;\n /**\n * Why it expired: `grant_expired` - the grant reached its expiry; `subscription_canceled` /\n * `subscription_charged_back` - an `included` grant whose subscription was canceled immediately,\n * or whose period's payment was lost to a chargeback, ended early (`expiresAt` is then the end).\n */\n reason: \"grant_expired\" | \"subscription_canceled\" | \"subscription_charged_back\";\n /** The `expire` ledger transaction. */\n transactionId: string;\n}\n/** `credit_reservation.expired` - a hold passed its expiry unsettled and was released. */\nexport interface CreditReservationExpiredData extends CreditEventBase {\n reservationId: string;\n operationKey: string;\n category: string;\n amount: string;\n expiresAt: string;\n}\n/** What every `credit_top_up.*` event says about the purchase and its payment. */\nexport interface CreditTopUpEventBase extends CreditEventBase {\n topUpId: string;\n packId: string;\n chargeId: string;\n /** What the payment collects, in minor units of `currency`. */\n amount: number;\n currency: string;\n /** The terms bought. */\n credits: string;\n bonusCredits: string;\n}\n/** `credit_top_up.succeeded` - the payment succeeded and its credits (and any bonus) were granted. */\nexport interface CreditTopUpSucceededData extends CreditTopUpEventBase {\n purchasedGrantId: string | null;\n bonusGrantId: string | null;\n}\n/**\n * `credit_top_up.failed` - the payment failed. A later payment on the same checkout can still\n * succeed it: `credit_top_up.succeeded` may follow for the same top-up.\n */\nexport type CreditTopUpFailedData = CreditTopUpEventBase;\n/** `credit_top_up.unfulfilled` - paid after the customer was erased: nothing granted, refund it. */\nexport type CreditTopUpUnfulfilledData = CreditTopUpEventBase;\n/**\n * `credit_top_up.refunded` - a refund or lost chargeback revoked the top-up's credits in\n * proportion to the money returned.\n */\nexport interface CreditTopUpRefundedData extends CreditTopUpEventBase {\n cause: \"refund\" | \"chargeback\";\n /** Money returned so far (refunds plus lost disputes, at most what was charged), minor units. */\n amountReturned: number;\n /** Microcredits this refund or chargeback revoked. */\n revokedNow: string;\n /** Microcredits revoked so far in all. */\n revoked: string;\n /** Of them, what the customer had already spent and nothing has paid back. */\n shortfall: string;\n}\n\n/**\n * The verified webhook envelope billow POSTs to your endpoint - a discriminated union on\n * {@link WebhookEvent.type}. `switch` (or a {@link WebhookHandlers} map) on `type` and `data`\n * narrows to the exact payload for that event.\n *\n * switch (event.type) {\n * case \"invoice.paid\": settle(event.data.invoiceId); break; // InvoiceEventData\n * case \"subscription.canceled\": revoke(event.data.customerExternalId); break;\n * }\n */\nexport type WebhookEventDataMap = {\n [K in \"charge.succeeded\" | \"charge.failed\"]: ChargeEventData;\n} & {\n \"charge.refunded\": ChargeRefundedEventData;\n} & {\n [K in \"charge.disputed\" | \"charge.dispute_resolved\"]: ChargeDisputeEventData;\n} & {\n [K in InvoiceEventType]: InvoiceEventData;\n} & {\n [K in SubscriptionEventType]: SubscriptionEventData;\n} & {\n [K in EntitlementEventType]: EntitlementEventData;\n} & {\n \"usage.threshold_reached\": UsageThresholdReachedData;\n \"usage.alert\": UsageAlertData;\n \"usage.cap_reached\": UsageCapReachedData;\n} & {\n \"credit_balance.threshold_crossed\": CreditThresholdCrossedData;\n \"credit_grant.created\": CreditGrantCreatedData;\n \"credit_grant.expired\": CreditGrantExpiredData;\n \"credit_reservation.expired\": CreditReservationExpiredData;\n \"credit_top_up.succeeded\": CreditTopUpSucceededData;\n \"credit_top_up.failed\": CreditTopUpFailedData;\n \"credit_top_up.refunded\": CreditTopUpRefundedData;\n \"credit_top_up.unfulfilled\": CreditTopUpUnfulfilledData;\n} & {\n \"hosted_domain.activated\": HostedDomainActivatedData;\n \"hosted_domain.dns_failing\": HostedDomainDnsFailingData;\n \"hosted_domain.reassignment_pending\": HostedDomainReassignmentPendingData;\n \"hosted_domain.disabled\": HostedDomainDisabledData;\n};\n\n/** One distributive union member per event literal, derived from the payload map. */\nexport type WebhookEvent = {\n [K in BillowEventType]: WebhookEventEnvelope & { type: K; data: WebhookEventDataMap[K] };\n}[BillowEventType];\n\nexport interface VerifyOptions {\n /**\n * Max age (seconds) of a delivery's signature timestamp before it's rejected as\n * a possible replay. Default 300 (5 minutes).\n */\n toleranceSeconds?: number;\n}\n\n/** Thrown by {@link constructEvent} when a delivery can't be trusted. */\nexport class WebhookVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhookVerificationError\";\n }\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\nconst encoder = new TextEncoder();\n\ninterface ParsedHeader {\n timestamp: number;\n signatures: string[];\n}\n\n/** Parse `t=<unix>,v1=<hex>[,v1=<hex>…]` - multiple v1 supports key rotation. */\nfunction parseHeader(header: string): ParsedHeader | null {\n let timestamp = Number.NaN;\n const signatures: string[] = [];\n for (const part of header.split(\",\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.slice(0, eq).trim();\n const value = part.slice(eq + 1).trim();\n if (key === \"t\") timestamp = Number(value);\n else if (key === \"v1\") signatures.push(value);\n }\n if (!Number.isFinite(timestamp) || signatures.length === 0) return null;\n return { timestamp, signatures };\n}\n\nasync function hmacHex(secret: string, payload: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const sig = await crypto.subtle.sign(\"HMAC\", key, encoder.encode(payload));\n return Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/** Constant-time compare of two equal-length hex strings (no early exit). */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let mismatch = 0;\n for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return mismatch === 0;\n}\n\n/**\n * Return `true` if `header` is a valid signature for `payload` under `secret` and\n * within the freshness window. Never throws - use {@link constructEvent} when you\n * want the parsed event and a thrown error on failure. Pass the RAW request body.\n */\nexport async function verifySignature(\n payload: string,\n header: string | null | undefined,\n secret: string,\n opts: VerifyOptions = {},\n): Promise<boolean> {\n if (!header) return false;\n const parsed = parseHeader(header);\n if (!parsed) return false;\n\n const tolerance = opts.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - parsed.timestamp) > tolerance) return false;\n\n const expected = await hmacHex(secret, `${parsed.timestamp}.${payload}`);\n return parsed.signatures.some((sig) => timingSafeEqual(sig, expected));\n}\n\n/**\n * Produce a valid `billow-signature` header for `payload` under `secret` - the inverse of\n * {@link verifySignature}. Use it to exercise your webhook endpoint in tests without\n * reverse-engineering the signing scheme. Pass the RAW body you will POST. `timestamp` (unix\n * seconds) defaults to now; pass a fixed value for a deterministic - or intentionally stale\n * (replay) - signature.\n *\n * const header = await signWebhook(rawBody, endpointSecret);\n * await fetch(url, { method: \"POST\", headers: { \"billow-signature\": header }, body: rawBody });\n */\nexport async function signWebhook(\n payload: string,\n secret: string,\n opts: { timestamp?: number } = {},\n): Promise<string> {\n const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1000);\n const signature = await hmacHex(secret, `${timestamp}.${payload}`);\n return `t=${timestamp},v1=${signature}`;\n}\n\n/**\n * Verify `payload` against the `billow-signature` header and return the parsed,\n * typed {@link WebhookEvent}. Throws {@link WebhookVerificationError} if the\n * signature is invalid, the timestamp is outside the freshness window, or the body\n * isn't a billow envelope. Always pass the RAW request body - a re-serialized\n * parsed body will not match.\n */\nexport async function constructEvent(\n payload: string,\n header: string | null | undefined,\n secret: string,\n opts: VerifyOptions = {},\n): Promise<WebhookEvent> {\n if (!(await verifySignature(payload, header, secret, opts))) {\n throw new WebhookVerificationError(\"billow webhook signature verification failed\");\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n throw new WebhookVerificationError(\"billow webhook payload is not valid JSON\");\n }\n if (!isWebhookEvent(parsed)) {\n throw new WebhookVerificationError(\"billow webhook payload is not a valid event envelope\");\n }\n return parsed;\n}\n\n/** A handler for one verified webhook event. May be async; a throw propagates to the caller. */\nexport type WebhookHandler = (event: WebhookEvent) => void | Promise<void>;\n\n/**\n * A map of per-event-type handlers plus an optional `onEvent` fallback. Each handler receives\n * the event NARROWED to its type, so `data` is the exact payload for that event\n * (e.g. `\"invoice.paid\": (e) => settle(e.data.invoiceId)`). Keyed by the literal event type so\n * it stays in lock-step with the contract - a new event type is dispatchable with no parallel\n * `onFooBar` name registry to keep in sync.\n */\nexport type WebhookHandlers = {\n [K in BillowEventType]?: (event: Extract<WebhookEvent, { type: K }>) => void | Promise<void>;\n} & {\n /** Called for any event that has no specific handler (receives the full union). */\n onEvent?: WebhookHandler;\n};\n\n/**\n * Build a reusable dispatcher bound to your endpoint secret + handlers: it verifies a\n * delivery (via {@link constructEvent}) then routes the typed event to the matching\n * handler, falling back to `onEvent`. Returns the verified event so the caller can log\n * or ack it. Throws {@link WebhookVerificationError} on an untrusted delivery (return a\n * 4xx) and propagates any error a handler throws (return a 5xx so billow retries).\n * Pass the RAW request body - a re-serialized parsed body won't match the signature.\n *\n * const dispatch = createWebhookDispatcher({\n * secret: process.env.BILLOW_WEBHOOK_SECRET,\n * handlers: {\n * \"invoice.paid\": (e) => fulfil(e.data.invoiceId),\n * \"subscription.canceled\": (e) => revoke(e.data),\n * onEvent: (e) => log(e.type),\n * },\n * });\n * // per request:\n * const event = await dispatch(rawBody, req.headers[\"billow-signature\"]);\n */\nexport function createWebhookDispatcher(config: {\n secret: string;\n handlers: WebhookHandlers;\n toleranceSeconds?: number;\n}): (payload: string, signatureHeader: string | null | undefined) => Promise<WebhookEvent> {\n const { secret, handlers, toleranceSeconds } = config;\n const opts = toleranceSeconds === undefined ? undefined : { toleranceSeconds };\n return async (payload, signatureHeader) => {\n const event = await constructEvent(payload, signatureHeader, secret, opts);\n // Own-property lookup, NOT `handlers[event.type]` directly: `event.type` is only\n // runtime-typed as a string, so a value colliding with an inherited key\n // (\"toString\"/\"constructor\"/\"__proto__\") would otherwise resolve to a prototype\n // method and be invoked instead of routing to `onEvent`. (Not reachable via a\n // trusted delivery - the type is HMAC-verified above and billow only emits fixed\n // event types - but the fallback contract must hold for any signature-valid type.)\n // The looked-up handler expects only its own narrowed variant; cast it to accept the full\n // union so we can invoke it with `event` - safe because the handler registered under key\n // `event.type` only ever receives the matching variant at runtime.\n const own = Object.hasOwn(handlers, event.type)\n ? (handlers[event.type] as WebhookHandler | undefined)\n : undefined;\n const handler = own ?? handlers.onEvent;\n if (handler) await handler(event);\n return event;\n };\n}\n\nfunction isWebhookEvent(value: unknown): value is WebhookEvent {\n if (typeof value !== \"object\" || value === null) return false;\n const e = value as {\n id?: unknown;\n version?: unknown;\n type?: unknown;\n createdAt?: unknown;\n data?: unknown;\n };\n return (\n typeof e.id === \"string\" &&\n typeof e.version === \"number\" &&\n typeof e.type === \"string\" &&\n typeof e.createdAt === \"string\" &&\n typeof e.data === \"object\" &&\n e.data !== null\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/webhook-event-types.ts","../src/webhooks.ts"],"names":[],"mappings":"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","file":"webhooks.cjs","sourcesContent":["/**\n * The webhook event catalog: every event type billow emits, and its families as types and runtime\n * arrays (for subscription pickers and exhaustive routing). Re-exported by ./webhooks.ts.\n */\n\n/**\n * Every event type billow emits, in contract order. Mirrors `@billow/core`'s\n * EVENT_TYPES - exported as a runtime array so UIs can render a subscription picker.\n */\nexport const WEBHOOK_EVENT_TYPES = [\n \"charge.succeeded\",\n \"charge.failed\",\n \"charge.refunded\",\n \"charge.refund_reversed\",\n \"charge.disputed\",\n \"charge.dispute_resolved\",\n \"invoice.paid\",\n \"invoice.payment_failed\",\n \"subscription.trial_started\",\n \"subscription.activated\",\n \"subscription.activation_failed\",\n \"subscription.renewed\",\n \"subscription.past_due\",\n \"subscription.payment_failed\",\n \"subscription.requires_payment_method\",\n \"subscription.unpaid\",\n \"subscription.paused\",\n \"subscription.resumed\",\n \"subscription.updated\",\n \"subscription.cancel_scheduled\",\n \"subscription.cancel_resumed\",\n \"subscription.downgrade_scheduled\",\n \"subscription.downgrade_canceled\",\n \"subscription.canceled\",\n \"subscription.ended\",\n \"entitlement.granted\",\n \"entitlement.reset\",\n \"usage.threshold_reached\",\n \"usage.alert\",\n \"usage.cap_reached\",\n \"credit_balance.threshold_crossed\",\n \"credit_grant.created\",\n \"credit_grant.expired\",\n \"credit_reservation.expired\",\n \"credit_top_up.succeeded\",\n \"credit_top_up.failed\",\n \"credit_top_up.refunded\",\n \"credit_top_up.unfulfilled\",\n \"hosted_domain.activated\",\n \"hosted_domain.dns_failing\",\n \"hosted_domain.reassignment_pending\",\n \"hosted_domain.disabled\",\n] as const;\n\n/** A valid billow event type (the compile-time freeze derived from the array above). */\nexport type BillowEventType = (typeof WEBHOOK_EVENT_TYPES)[number];\n\n/** The `charge.*` subset of {@link BillowEventType}. */\nexport type ChargeEventType = Extract<BillowEventType, `charge.${string}`>;\n/** The `invoice.*` subset of {@link BillowEventType}. */\nexport type InvoiceEventType = Extract<BillowEventType, `invoice.${string}`>;\n/** The `subscription.*` subset of {@link BillowEventType} - the events that change a subscription. */\nexport type SubscriptionEventType = Extract<BillowEventType, `subscription.${string}`>;\n/** The `entitlement.*` subset of {@link BillowEventType}. */\nexport type EntitlementEventType = Extract<BillowEventType, `entitlement.${string}`>;\n/** The `usage.*` subset of {@link BillowEventType}. */\nexport type UsageEventType = Extract<BillowEventType, `usage.${string}`>;\n/** The prepaid credits subset of {@link BillowEventType} (`credit_balance.*`, `credit_grant.*`, ...). */\nexport type CreditEventType = Extract<BillowEventType, `credit_${string}`>;\n/** The `hosted_domain.*` subset of {@link BillowEventType}: custom portal domains. */\nexport type HostedDomainEventType = Extract<BillowEventType, `hosted_domain.${string}`>;\n\n/** The subset of {@link WEBHOOK_EVENT_TYPES} sharing `prefix`, as a runtime array (order preserved). */\nfunction eventTypesWithPrefix<P extends string>(\n prefix: P,\n): readonly Extract<BillowEventType, `${P}${string}`>[] {\n return WEBHOOK_EVENT_TYPES.filter((type): type is Extract<BillowEventType, `${P}${string}`> =>\n type.startsWith(prefix),\n );\n}\n\n/** The `charge.*` event types, as a runtime array (for pickers / exhaustive routing). */\nexport const CHARGE_EVENT_TYPES: readonly ChargeEventType[] = eventTypesWithPrefix(\"charge.\");\n/** The `invoice.*` event types, as a runtime array. */\nexport const INVOICE_EVENT_TYPES: readonly InvoiceEventType[] = eventTypesWithPrefix(\"invoice.\");\n/**\n * The subscription lifecycle event types - the `subscription.*` subset of\n * {@link WEBHOOK_EVENT_TYPES}, as a runtime array. Use it (or {@link isSubscriptionEvent})\n * to select the events that affect a subscription mirror without a brittle string match.\n */\nexport const SUBSCRIPTION_EVENT_TYPES: readonly SubscriptionEventType[] =\n eventTypesWithPrefix(\"subscription.\");\n/** The `entitlement.*` event types, as a runtime array. */\nexport const ENTITLEMENT_EVENT_TYPES: readonly EntitlementEventType[] =\n eventTypesWithPrefix(\"entitlement.\");\n/** The `usage.*` event types, as a runtime array. */\nexport const USAGE_EVENT_TYPES: readonly UsageEventType[] = eventTypesWithPrefix(\"usage.\");\n/** The prepaid credits event types, as a runtime array. */\nexport const CREDIT_EVENT_TYPES: readonly CreditEventType[] = eventTypesWithPrefix(\"credit_\");\n/** The `hosted_domain.*` event types, as a runtime array. */\nexport const HOSTED_DOMAIN_EVENT_TYPES: readonly HostedDomainEventType[] =\n eventTypesWithPrefix(\"hosted_domain.\");\n\n/** Whether `type` is a subscription lifecycle event (`subscription.*`); narrows the type. */\nexport function isSubscriptionEvent(type: string): type is SubscriptionEventType {\n return type.startsWith(\"subscription.\");\n}\n","/**\n * Verify and parse billow webhook deliveries.\n *\n * billow signs each delivery with HMAC-SHA256 over `\"<timestamp>.<rawBody>\"` and\n * sends it in the `billow-signature` header as `t=<unix-seconds>,v1=<hex>`. The\n * timestamp lets you reject replays outside a freshness window.\n *\n * Verification is async and uses the Web Crypto API (`globalThis.crypto.subtle`),\n * so the same code runs in Node 20+, edge runtimes, Cloudflare Workers, Deno, and\n * Bun - it never imports `node:crypto`.\n *\n * import { constructEvent } from \"@usebillow/sdk/webhooks\";\n *\n * const event = await constructEvent(rawBody, req.headers[\"billow-signature\"], secret);\n * switch (event.type) {\n * case \"invoice.paid\": // …\n * }\n */\n\nimport type { CreditGrantKind, CreditThresholdLevel } from \"./types/credits\";\nimport type {\n HostedDomainActivatedData,\n HostedDomainDisabledData,\n HostedDomainDnsFailingData,\n HostedDomainReassignmentPendingData,\n} from \"./types/hosted-domains\";\nimport type {\n BillowEventType,\n EntitlementEventType,\n InvoiceEventType,\n SubscriptionEventType,\n} from \"./webhook-event-types\";\n\nexport * from \"./webhook-event-types\";\n\n/**\n * The fields every webhook envelope carries. Each {@link WebhookEvent} variant intersects\n * this with its own `type` + payload-typed `data`.\n */\nexport interface WebhookEventEnvelope {\n /** Stable event id - your idempotency key (delivery is at-least-once). */\n id: string;\n /** Envelope schema version. */\n version: number;\n /** ISO-8601 timestamp of when the event was recorded. */\n createdAt: string;\n}\n\n// --- Per-event-group payloads -------------------------------------------------------------\n// Shapes mirror what the server emits (packages/server/src/services: charges.ts, billing.ts,\n// renewal/*, subscriptions/*, entitlements/*). Grouped where a group's emitters are uniform;\n// `usage.*` is split per event because its emitters diverge. `customerExternalId` is added to\n// every customer-scoped payload by the server's outbox emitter (events.ts).\n\n/** `charge.succeeded` / `charge.failed`. */\nexport interface ChargeEventData {\n chargeId: string;\n /**\n * `false` on a `charge.failed` that closed a checkout its customer abandoned long before: no\n * \"payment failed\" email was sent to the customer. Absent otherwise.\n */\n notifyCustomer?: false;\n}\n/** `charge.refunded`, emitted once for each succeeded refund transition. */\nexport interface ChargeRefundedEventData {\n chargeId: string;\n refundId: string;\n amount: number;\n cumulativeAmount: number;\n currency: string;\n /**\n * The credit note issued with this success. A refund settled succeeded again after a reversal\n * gets a new one, so it tells the two successes apart. Absent on events emitted before it was\n * added.\n */\n creditNoteNumber?: string | null;\n}\n/**\n * `charge.refund_reversed`, emitted once when a succeeded refund the provider reversed (the\n * customer's bank returned the money) is resolved as not refunded: the refund is failed, its credit\n * note `voidedCreditNoteNumber` is voided, and `amount` is refundable again.\n */\nexport interface ChargeRefundReversedEventData {\n chargeId: string;\n refundId: string;\n amount: number;\n currency: string;\n voidedCreditNoteNumber: string | null;\n}\n/** Stable dispute facts shared by the opened and resolved events. */\nexport interface ChargeDisputeEventData {\n chargeId: string;\n disputeId: string;\n amount: number;\n currency: string;\n reason: string | null;\n state: \"open\" | \"won\" | \"lost\";\n}\n/** `invoice.paid` / `invoice.payment_failed`. */\nexport interface InvoiceEventData {\n invoiceId: string;\n chargeId: string;\n /**\n * `false` on an `invoice.payment_failed` that closed a checkout its customer abandoned long\n * before: no \"payment failed\" email was sent to the customer. Absent otherwise.\n */\n notifyCustomer?: false;\n}\n/** Any `subscription.*` lifecycle event. */\nexport interface SubscriptionEventData {\n subscriptionId: string;\n customerId: string;\n customerExternalId: string;\n /** The subscription's plan - so a consumer knows which product the event is about. */\n productId: string;\n productSlug: string;\n /** The product's metadata map (Stripe-style); `{}` when the plan carries none. */\n productMetadata: Record<string, string>;\n /** Set on dunning events that reference the unpaid invoice (`unpaid`, `requires_payment_method`). */\n invoiceId?: string;\n /** Set on lifecycle-ending events (`ended`, downgrade-driven `updated`). */\n reason?: string;\n /** Present when a downgrade is scheduled; null when that schedule is cleared. */\n scheduledChange?: {\n productVersionId: string;\n productId: string;\n productSlug: string;\n productName: string;\n effectiveAt: string | null;\n } | null;\n}\n/** `entitlement.granted` / `entitlement.reset`. */\nexport interface EntitlementEventData {\n subscriptionId: string;\n customerId: string;\n customerExternalId: string;\n featureId: string;\n /** Remaining allowance; `null` when the feature is unlimited. */\n balance: number | null;\n /** Whether the feature grants unlimited usage. */\n unlimited: boolean;\n}\n/** Fields shared by every `usage.*` event. */\nexport interface UsageEventBase {\n subscriptionId: string;\n customerId: string;\n customerExternalId: string;\n /** Feature slug. */\n feature: string;\n featureName: string;\n /** Aggregate usage after this step. */\n used: number;\n /** Included allowance; `null` when unlimited. */\n allowance: number | null;\n /** Remaining allowance; `null` when unlimited. */\n balance: number | null;\n}\n/** `usage.threshold_reached` - a built-in usage threshold was crossed. */\nexport interface UsageThresholdReachedData extends UsageEventBase {\n featureId: string;\n /** The threshold crossed (currently only the included allowance being exhausted). */\n level: \"exhausted\";\n}\n/** `usage.alert` - a configured usage alert fired. */\nexport interface UsageAlertData extends UsageEventBase {\n metric: string;\n threshold: number;\n recurring: boolean;\n notifyCustomer: boolean;\n}\n/** `usage.cap_reached` - a configured spend/usage cap was reached. */\nexport interface UsageCapReachedData extends UsageEventBase {\n metric: string;\n cap: number;\n capAt: number;\n}\n\n// Prepaid credits: every credit quantity is a decimal string of microcredits, never a number.\n// Payloads carry ids, codes and quantities, never a grant's, reservation's or top-up's reason or\n// metadata.\n\n/** Fields shared by every prepaid credits event. */\nexport interface CreditEventBase {\n customerId: string;\n customerExternalId: string;\n}\n/**\n * `credit_balance.threshold_crossed` - the balance moved to another level, in either direction\n * (raise a low-balance banner, or clear it). `exhausted` when nothing is spendable; otherwise\n * `critical` / `low` at or below the percentages of `thresholds.reference`, else `normal`.\n */\nexport interface CreditThresholdCrossedData extends CreditEventBase {\n level: CreditThresholdLevel;\n previousLevel: CreditThresholdLevel;\n /** The balance the level was judged on, as of `asOf`; re-read the balance for truth. */\n balance: { spendable: string; held: string; asOf: string };\n thresholds: { lowPercent: number; criticalPercent: number; reference: string | null };\n}\n/** `credit_grant.created` - credits were granted. */\nexport interface CreditGrantCreatedData extends CreditEventBase {\n grantId: string;\n kind: CreditGrantKind;\n amount: string;\n /** For an `included` grant, its window's end. */\n expiresAt: string | null;\n eligibility: string[] | null;\n topUpId: string | null;\n /** An `included` grant's subscription; null for every other kind. */\n subscriptionId: string | null;\n /** The billing period an `included` grant's window belongs to; null for every other kind. */\n periodStart: string | null;\n periodEnd: string | null;\n}\n/**\n * `credit_grant.expired` - one expiry took `expired` credits off a grant: its unheld credits when\n * it expired (or when an early end of its included window brought its expiry forward), or a held\n * part that came back to it afterwards (one more event each).\n */\nexport interface CreditGrantExpiredData extends CreditEventBase {\n grantId: string;\n kind: CreditGrantKind;\n expiresAt: string | null;\n expired: string;\n /**\n * Why it expired: `grant_expired` - the grant reached its expiry; `subscription_canceled` /\n * `subscription_charged_back` - an `included` grant whose subscription was canceled immediately,\n * or whose period's payment was lost to a chargeback, ended early (`expiresAt` is then the end).\n */\n reason: \"grant_expired\" | \"subscription_canceled\" | \"subscription_charged_back\";\n /** The `expire` ledger transaction. */\n transactionId: string;\n}\n/** `credit_reservation.expired` - a hold passed its expiry unsettled and was released. */\nexport interface CreditReservationExpiredData extends CreditEventBase {\n reservationId: string;\n operationKey: string;\n category: string;\n amount: string;\n expiresAt: string;\n}\n/** What every `credit_top_up.*` event says about the purchase and its payment. */\nexport interface CreditTopUpEventBase extends CreditEventBase {\n topUpId: string;\n packId: string;\n chargeId: string;\n /** What the payment collects, in minor units of `currency`. */\n amount: number;\n currency: string;\n /** The terms bought. */\n credits: string;\n bonusCredits: string;\n}\n/** `credit_top_up.succeeded` - the payment succeeded and its credits (and any bonus) were granted. */\nexport interface CreditTopUpSucceededData extends CreditTopUpEventBase {\n purchasedGrantId: string | null;\n bonusGrantId: string | null;\n}\n/**\n * `credit_top_up.failed` - the payment failed. A later payment on the same checkout can still\n * succeed it: `credit_top_up.succeeded` may follow for the same top-up.\n */\nexport type CreditTopUpFailedData = CreditTopUpEventBase;\n/** `credit_top_up.unfulfilled` - paid after the customer was erased: nothing granted, refund it. */\nexport type CreditTopUpUnfulfilledData = CreditTopUpEventBase;\n/**\n * `credit_top_up.refunded` - a refund or lost chargeback revoked the top-up's credits in\n * proportion to the money returned.\n */\nexport interface CreditTopUpRefundedData extends CreditTopUpEventBase {\n cause: \"refund\" | \"chargeback\";\n /** Money returned so far (refunds plus lost disputes, at most what was charged), minor units. */\n amountReturned: number;\n /** Microcredits this refund or chargeback revoked. */\n revokedNow: string;\n /** Microcredits revoked so far in all. */\n revoked: string;\n /** Of them, what the customer had already spent and nothing has paid back. */\n shortfall: string;\n}\n\n/**\n * The verified webhook envelope billow POSTs to your endpoint - a discriminated union on\n * {@link WebhookEvent.type}. `switch` (or a {@link WebhookHandlers} map) on `type` and `data`\n * narrows to the exact payload for that event.\n *\n * switch (event.type) {\n * case \"invoice.paid\": settle(event.data.invoiceId); break; // InvoiceEventData\n * case \"subscription.canceled\": revoke(event.data.customerExternalId); break;\n * }\n */\nexport type WebhookEventDataMap = {\n [K in \"charge.succeeded\" | \"charge.failed\"]: ChargeEventData;\n} & {\n \"charge.refunded\": ChargeRefundedEventData;\n \"charge.refund_reversed\": ChargeRefundReversedEventData;\n} & {\n [K in \"charge.disputed\" | \"charge.dispute_resolved\"]: ChargeDisputeEventData;\n} & {\n [K in InvoiceEventType]: InvoiceEventData;\n} & {\n [K in SubscriptionEventType]: SubscriptionEventData;\n} & {\n [K in EntitlementEventType]: EntitlementEventData;\n} & {\n \"usage.threshold_reached\": UsageThresholdReachedData;\n \"usage.alert\": UsageAlertData;\n \"usage.cap_reached\": UsageCapReachedData;\n} & {\n \"credit_balance.threshold_crossed\": CreditThresholdCrossedData;\n \"credit_grant.created\": CreditGrantCreatedData;\n \"credit_grant.expired\": CreditGrantExpiredData;\n \"credit_reservation.expired\": CreditReservationExpiredData;\n \"credit_top_up.succeeded\": CreditTopUpSucceededData;\n \"credit_top_up.failed\": CreditTopUpFailedData;\n \"credit_top_up.refunded\": CreditTopUpRefundedData;\n \"credit_top_up.unfulfilled\": CreditTopUpUnfulfilledData;\n} & {\n \"hosted_domain.activated\": HostedDomainActivatedData;\n \"hosted_domain.dns_failing\": HostedDomainDnsFailingData;\n \"hosted_domain.reassignment_pending\": HostedDomainReassignmentPendingData;\n \"hosted_domain.disabled\": HostedDomainDisabledData;\n};\n\n/** One distributive union member per event literal, derived from the payload map. */\nexport type WebhookEvent = {\n [K in BillowEventType]: WebhookEventEnvelope & { type: K; data: WebhookEventDataMap[K] };\n}[BillowEventType];\n\nexport interface VerifyOptions {\n /**\n * Max age (seconds) of a delivery's signature timestamp before it's rejected as\n * a possible replay. Default 300 (5 minutes).\n */\n toleranceSeconds?: number;\n}\n\n/** Thrown by {@link constructEvent} when a delivery can't be trusted. */\nexport class WebhookVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhookVerificationError\";\n }\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300;\nconst encoder = new TextEncoder();\n\ninterface ParsedHeader {\n timestamp: number;\n signatures: string[];\n}\n\n/** Parse `t=<unix>,v1=<hex>[,v1=<hex>…]` - multiple v1 supports key rotation. */\nfunction parseHeader(header: string): ParsedHeader | null {\n let timestamp = Number.NaN;\n const signatures: string[] = [];\n for (const part of header.split(\",\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const key = part.slice(0, eq).trim();\n const value = part.slice(eq + 1).trim();\n if (key === \"t\") timestamp = Number(value);\n else if (key === \"v1\") signatures.push(value);\n }\n if (!Number.isFinite(timestamp) || signatures.length === 0) return null;\n return { timestamp, signatures };\n}\n\nasync function hmacHex(secret: string, payload: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n \"raw\",\n encoder.encode(secret),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const sig = await crypto.subtle.sign(\"HMAC\", key, encoder.encode(payload));\n return Array.from(new Uint8Array(sig), (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/** Constant-time compare of two equal-length hex strings (no early exit). */\nfunction timingSafeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let mismatch = 0;\n for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return mismatch === 0;\n}\n\n/**\n * Return `true` if `header` is a valid signature for `payload` under `secret` and\n * within the freshness window. Never throws - use {@link constructEvent} when you\n * want the parsed event and a thrown error on failure. Pass the RAW request body.\n */\nexport async function verifySignature(\n payload: string,\n header: string | null | undefined,\n secret: string,\n opts: VerifyOptions = {},\n): Promise<boolean> {\n if (!header) return false;\n const parsed = parseHeader(header);\n if (!parsed) return false;\n\n const tolerance = opts.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - parsed.timestamp) > tolerance) return false;\n\n const expected = await hmacHex(secret, `${parsed.timestamp}.${payload}`);\n return parsed.signatures.some((sig) => timingSafeEqual(sig, expected));\n}\n\n/**\n * Produce a valid `billow-signature` header for `payload` under `secret` - the inverse of\n * {@link verifySignature}. Use it to exercise your webhook endpoint in tests without\n * reverse-engineering the signing scheme. Pass the RAW body you will POST. `timestamp` (unix\n * seconds) defaults to now; pass a fixed value for a deterministic - or intentionally stale\n * (replay) - signature.\n *\n * const header = await signWebhook(rawBody, endpointSecret);\n * await fetch(url, { method: \"POST\", headers: { \"billow-signature\": header }, body: rawBody });\n */\nexport async function signWebhook(\n payload: string,\n secret: string,\n opts: { timestamp?: number } = {},\n): Promise<string> {\n const timestamp = opts.timestamp ?? Math.floor(Date.now() / 1000);\n const signature = await hmacHex(secret, `${timestamp}.${payload}`);\n return `t=${timestamp},v1=${signature}`;\n}\n\n/**\n * Verify `payload` against the `billow-signature` header and return the parsed,\n * typed {@link WebhookEvent}. Throws {@link WebhookVerificationError} if the\n * signature is invalid, the timestamp is outside the freshness window, or the body\n * isn't a billow envelope. Always pass the RAW request body - a re-serialized\n * parsed body will not match.\n */\nexport async function constructEvent(\n payload: string,\n header: string | null | undefined,\n secret: string,\n opts: VerifyOptions = {},\n): Promise<WebhookEvent> {\n if (!(await verifySignature(payload, header, secret, opts))) {\n throw new WebhookVerificationError(\"billow webhook signature verification failed\");\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n throw new WebhookVerificationError(\"billow webhook payload is not valid JSON\");\n }\n if (!isWebhookEvent(parsed)) {\n throw new WebhookVerificationError(\"billow webhook payload is not a valid event envelope\");\n }\n return parsed;\n}\n\n/** A handler for one verified webhook event. May be async; a throw propagates to the caller. */\nexport type WebhookHandler = (event: WebhookEvent) => void | Promise<void>;\n\n/**\n * A map of per-event-type handlers plus an optional `onEvent` fallback. Each handler receives\n * the event NARROWED to its type, so `data` is the exact payload for that event\n * (e.g. `\"invoice.paid\": (e) => settle(e.data.invoiceId)`). Keyed by the literal event type so\n * it stays in lock-step with the contract - a new event type is dispatchable with no parallel\n * `onFooBar` name registry to keep in sync.\n */\nexport type WebhookHandlers = {\n [K in BillowEventType]?: (event: Extract<WebhookEvent, { type: K }>) => void | Promise<void>;\n} & {\n /** Called for any event that has no specific handler (receives the full union). */\n onEvent?: WebhookHandler;\n};\n\n/**\n * Build a reusable dispatcher bound to your endpoint secret + handlers: it verifies a\n * delivery (via {@link constructEvent}) then routes the typed event to the matching\n * handler, falling back to `onEvent`. Returns the verified event so the caller can log\n * or ack it. Throws {@link WebhookVerificationError} on an untrusted delivery (return a\n * 4xx) and propagates any error a handler throws (return a 5xx so billow retries).\n * Pass the RAW request body - a re-serialized parsed body won't match the signature.\n *\n * const dispatch = createWebhookDispatcher({\n * secret: process.env.BILLOW_WEBHOOK_SECRET,\n * handlers: {\n * \"invoice.paid\": (e) => fulfil(e.data.invoiceId),\n * \"subscription.canceled\": (e) => revoke(e.data),\n * onEvent: (e) => log(e.type),\n * },\n * });\n * // per request:\n * const event = await dispatch(rawBody, req.headers[\"billow-signature\"]);\n */\nexport function createWebhookDispatcher(config: {\n secret: string;\n handlers: WebhookHandlers;\n toleranceSeconds?: number;\n}): (payload: string, signatureHeader: string | null | undefined) => Promise<WebhookEvent> {\n const { secret, handlers, toleranceSeconds } = config;\n const opts = toleranceSeconds === undefined ? undefined : { toleranceSeconds };\n return async (payload, signatureHeader) => {\n const event = await constructEvent(payload, signatureHeader, secret, opts);\n // Own-property lookup, NOT `handlers[event.type]` directly: `event.type` is only\n // runtime-typed as a string, so a value colliding with an inherited key\n // (\"toString\"/\"constructor\"/\"__proto__\") would otherwise resolve to a prototype\n // method and be invoked instead of routing to `onEvent`. (Not reachable via a\n // trusted delivery - the type is HMAC-verified above and billow only emits fixed\n // event types - but the fallback contract must hold for any signature-valid type.)\n // The looked-up handler expects only its own narrowed variant; cast it to accept the full\n // union so we can invoke it with `event` - safe because the handler registered under key\n // `event.type` only ever receives the matching variant at runtime.\n const own = Object.hasOwn(handlers, event.type)\n ? (handlers[event.type] as WebhookHandler | undefined)\n : undefined;\n const handler = own ?? handlers.onEvent;\n if (handler) await handler(event);\n return event;\n };\n}\n\nfunction isWebhookEvent(value: unknown): value is WebhookEvent {\n if (typeof value !== \"object\" || value === null) return false;\n const e = value as {\n id?: unknown;\n version?: unknown;\n type?: unknown;\n createdAt?: unknown;\n data?: unknown;\n };\n return (\n typeof e.id === \"string\" &&\n typeof e.version === \"number\" &&\n typeof e.type === \"string\" &&\n typeof e.createdAt === \"string\" &&\n typeof e.data === \"object\" &&\n e.data !== null\n );\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { C as CreditGrantKind, a as CreditThresholdLevel, H as HostedDomainActivatedData, b as HostedDomainDnsFailingData, c as HostedDomainReassignmentPendingData, d as HostedDomainDisabledData } from './hosted-domains-zWm2srOk.cjs';
2
- import './index.d-DSEYhV2c.cjs';
1
+ import { C as CreditGrantKind, a as CreditThresholdLevel, H as HostedDomainActivatedData, b as HostedDomainDnsFailingData, c as HostedDomainReassignmentPendingData, d as HostedDomainDisabledData } from './hosted-domains-Cuk3j04R.cjs';
2
+ import './index.d-D5v2ETKF.cjs';
3
3
  import 'zod';
4
4
 
5
5
  /**
@@ -10,7 +10,7 @@ import 'zod';
10
10
  * Every event type billow emits, in contract order. Mirrors `@billow/core`'s
11
11
  * EVENT_TYPES - exported as a runtime array so UIs can render a subscription picker.
12
12
  */
13
- declare const WEBHOOK_EVENT_TYPES: readonly ["charge.succeeded", "charge.failed", "charge.refunded", "charge.disputed", "charge.dispute_resolved", "invoice.paid", "invoice.payment_failed", "subscription.trial_started", "subscription.activated", "subscription.activation_failed", "subscription.renewed", "subscription.past_due", "subscription.payment_failed", "subscription.requires_payment_method", "subscription.unpaid", "subscription.paused", "subscription.resumed", "subscription.updated", "subscription.cancel_scheduled", "subscription.cancel_resumed", "subscription.downgrade_scheduled", "subscription.downgrade_canceled", "subscription.canceled", "subscription.ended", "entitlement.granted", "entitlement.reset", "usage.threshold_reached", "usage.alert", "usage.cap_reached", "credit_balance.threshold_crossed", "credit_grant.created", "credit_grant.expired", "credit_reservation.expired", "credit_top_up.succeeded", "credit_top_up.failed", "credit_top_up.refunded", "credit_top_up.unfulfilled", "hosted_domain.activated", "hosted_domain.dns_failing", "hosted_domain.reassignment_pending", "hosted_domain.disabled"];
13
+ declare const WEBHOOK_EVENT_TYPES: readonly ["charge.succeeded", "charge.failed", "charge.refunded", "charge.refund_reversed", "charge.disputed", "charge.dispute_resolved", "invoice.paid", "invoice.payment_failed", "subscription.trial_started", "subscription.activated", "subscription.activation_failed", "subscription.renewed", "subscription.past_due", "subscription.payment_failed", "subscription.requires_payment_method", "subscription.unpaid", "subscription.paused", "subscription.resumed", "subscription.updated", "subscription.cancel_scheduled", "subscription.cancel_resumed", "subscription.downgrade_scheduled", "subscription.downgrade_canceled", "subscription.canceled", "subscription.ended", "entitlement.granted", "entitlement.reset", "usage.threshold_reached", "usage.alert", "usage.cap_reached", "credit_balance.threshold_crossed", "credit_grant.created", "credit_grant.expired", "credit_reservation.expired", "credit_top_up.succeeded", "credit_top_up.failed", "credit_top_up.refunded", "credit_top_up.unfulfilled", "hosted_domain.activated", "hosted_domain.dns_failing", "hosted_domain.reassignment_pending", "hosted_domain.disabled"];
14
14
  /** A valid billow event type (the compile-time freeze derived from the array above). */
15
15
  type BillowEventType = (typeof WEBHOOK_EVENT_TYPES)[number];
16
16
  /** The `charge.*` subset of {@link BillowEventType}. */
@@ -95,6 +95,24 @@ interface ChargeRefundedEventData {
95
95
  amount: number;
96
96
  cumulativeAmount: number;
97
97
  currency: string;
98
+ /**
99
+ * The credit note issued with this success. A refund settled succeeded again after a reversal
100
+ * gets a new one, so it tells the two successes apart. Absent on events emitted before it was
101
+ * added.
102
+ */
103
+ creditNoteNumber?: string | null;
104
+ }
105
+ /**
106
+ * `charge.refund_reversed`, emitted once when a succeeded refund the provider reversed (the
107
+ * customer's bank returned the money) is resolved as not refunded: the refund is failed, its credit
108
+ * note `voidedCreditNoteNumber` is voided, and `amount` is refundable again.
109
+ */
110
+ interface ChargeRefundReversedEventData {
111
+ chargeId: string;
112
+ refundId: string;
113
+ amount: number;
114
+ currency: string;
115
+ voidedCreditNoteNumber: string | null;
98
116
  }
99
117
  /** Stable dispute facts shared by the opened and resolved events. */
100
118
  interface ChargeDisputeEventData {
@@ -303,6 +321,7 @@ type WebhookEventDataMap = {
303
321
  [K in "charge.succeeded" | "charge.failed"]: ChargeEventData;
304
322
  } & {
305
323
  "charge.refunded": ChargeRefundedEventData;
324
+ "charge.refund_reversed": ChargeRefundReversedEventData;
306
325
  } & {
307
326
  [K in "charge.disputed" | "charge.dispute_resolved"]: ChargeDisputeEventData;
308
327
  } & {
@@ -417,4 +436,4 @@ declare function createWebhookDispatcher(config: {
417
436
  toleranceSeconds?: number;
418
437
  }): (payload: string, signatureHeader: string | null | undefined) => Promise<WebhookEvent>;
419
438
 
420
- export { type BillowEventType, CHARGE_EVENT_TYPES, CREDIT_EVENT_TYPES, type ChargeDisputeEventData, type ChargeEventData, type ChargeEventType, type ChargeRefundedEventData, type CreditEventBase, type CreditEventType, type CreditGrantCreatedData, type CreditGrantExpiredData, type CreditReservationExpiredData, type CreditThresholdCrossedData, type CreditTopUpEventBase, type CreditTopUpFailedData, type CreditTopUpRefundedData, type CreditTopUpSucceededData, type CreditTopUpUnfulfilledData, ENTITLEMENT_EVENT_TYPES, type EntitlementEventData, type EntitlementEventType, HOSTED_DOMAIN_EVENT_TYPES, type HostedDomainEventType, INVOICE_EVENT_TYPES, type InvoiceEventData, type InvoiceEventType, SUBSCRIPTION_EVENT_TYPES, type SubscriptionEventData, type SubscriptionEventType, USAGE_EVENT_TYPES, type UsageAlertData, type UsageCapReachedData, type UsageEventBase, type UsageEventType, type UsageThresholdReachedData, type VerifyOptions, WEBHOOK_EVENT_TYPES, type WebhookEvent, type WebhookEventDataMap, type WebhookEventEnvelope, type WebhookHandler, type WebhookHandlers, WebhookVerificationError, constructEvent, createWebhookDispatcher, isSubscriptionEvent, signWebhook, verifySignature };
439
+ export { type BillowEventType, CHARGE_EVENT_TYPES, CREDIT_EVENT_TYPES, type ChargeDisputeEventData, type ChargeEventData, type ChargeEventType, type ChargeRefundReversedEventData, type ChargeRefundedEventData, type CreditEventBase, type CreditEventType, type CreditGrantCreatedData, type CreditGrantExpiredData, type CreditReservationExpiredData, type CreditThresholdCrossedData, type CreditTopUpEventBase, type CreditTopUpFailedData, type CreditTopUpRefundedData, type CreditTopUpSucceededData, type CreditTopUpUnfulfilledData, ENTITLEMENT_EVENT_TYPES, type EntitlementEventData, type EntitlementEventType, HOSTED_DOMAIN_EVENT_TYPES, type HostedDomainEventType, INVOICE_EVENT_TYPES, type InvoiceEventData, type InvoiceEventType, SUBSCRIPTION_EVENT_TYPES, type SubscriptionEventData, type SubscriptionEventType, USAGE_EVENT_TYPES, type UsageAlertData, type UsageCapReachedData, type UsageEventBase, type UsageEventType, type UsageThresholdReachedData, type VerifyOptions, WEBHOOK_EVENT_TYPES, type WebhookEvent, type WebhookEventDataMap, type WebhookEventEnvelope, type WebhookHandler, type WebhookHandlers, WebhookVerificationError, constructEvent, createWebhookDispatcher, isSubscriptionEvent, signWebhook, verifySignature };