@boelabs/billete-sdk 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/client.d.ts +54 -0
- package/dist/client.js +102 -0
- package/dist/client.js.map +1 -0
- package/dist/errors-oT7VMFhd.d.ts +450 -0
- package/dist/events-BJuSt_4W.d.ts +568 -0
- package/dist/http-ChElV20o.js +124 -0
- package/dist/http-ChElV20o.js.map +1 -0
- package/dist/index.d.ts +198 -0
- package/dist/index.js +273 -0
- package/dist/index.js.map +1 -0
- package/dist/webhooks.d.ts +19 -0
- package/dist/webhooks.js +46 -0
- package/dist/webhooks.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-ChElV20o.js","names":[],"sources":["../src/errors.ts","../src/http.ts"],"sourcesContent":["import type { ApiErrorBody, ApiErrorType } from \"@boelabs/billete-contracts/public\";\n\n/** Any failed Billete request. `type`/`code` are stable and safe to branch on. */\nexport class BilleteError extends Error {\n readonly type: ApiErrorType | \"network\";\n readonly code: string;\n readonly status: number;\n readonly param?: string;\n readonly requestId?: string;\n\n constructor(\n body: Omit<ApiErrorBody, \"type\"> & { type: ApiErrorType | \"network\" },\n status: number,\n ) {\n super(body.message);\n this.name = \"BilleteError\";\n this.type = body.type;\n this.code = body.code;\n this.status = status;\n this.param = body.param;\n this.requestId = body.requestId;\n }\n}\n","import type { ApiErrorEnvelope, List } from \"@boelabs/billete-contracts/public\";\n\nimport { BilleteError } from \"./errors\";\n\n/** Replaced at build time with the package version; the source (tests, workspace) reads \"dev\". */\ndeclare const PACKAGE_VERSION: string | undefined;\nexport const SDK_VERSION = typeof PACKAGE_VERSION === \"string\" ? PACKAGE_VERSION : \"dev\";\nconst DEFAULT_BASE_URL = \"https://api.billete.run\";\nconst RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);\n\nexport interface TransportOptions {\n /** Defaults to `BILLETE_BASE_URL` on the server, then `https://api.billete.run`. Use\n * `http://localhost:9999` in development. */\n baseUrl?: string;\n /** Custom fetch (tests, proxies). Defaults to the global fetch. */\n fetch?: typeof globalThis.fetch;\n timeoutMs?: number;\n /** Retries for network errors, 429, 5xx and in-progress duplicates. Mutations retry safely. */\n maxRetries?: number;\n}\n\nexport interface RequestOptions {\n /** Reuse to make a mutation safe to retry; generated automatically when omitted. */\n idempotencyKey?: string;\n signal?: AbortSignal;\n}\n\n/** Query parameters; `undefined` values are omitted. */\ntype Query = object;\n\ninterface Request {\n method: \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\";\n path: string;\n query?: Query;\n body?: unknown;\n options?: RequestOptions;\n}\n\nfunction backoff(attempt: number, retryAfter: string | null) {\n const seconds = retryAfter ? Number(retryAfter) : Number.NaN;\n return Number.isFinite(seconds) ? seconds * 1_000 : Math.min(500 * 2 ** attempt, 8_000);\n}\n\nfunction pause(ms: number, signal?: AbortSignal) {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n reject(signal.reason);\n },\n { once: true },\n );\n });\n}\n\nfunction buildUrl(baseUrl: string, path: string, query?: Query) {\n const url = new URL(path, baseUrl);\n Object.entries(query ?? {})\n .filter(([, value]) => value !== undefined)\n .forEach(([key, value]) => url.searchParams.set(key, String(value)));\n return url;\n}\n\n/** A concurrent request with the same idempotency key is still running: worth retrying. */\nasync function inProgress(response: Response) {\n const body = (await response\n .clone()\n .json()\n .catch(() => null)) as ApiErrorEnvelope | null;\n return body?.error.code === \"idempotency_request_in_progress\";\n}\n\nasync function toError(response: Response): Promise<BilleteError> {\n const body = (await response.json().catch(() => null)) as ApiErrorEnvelope | null;\n return new BilleteError(\n body?.error ?? {\n type: \"internal\",\n code: \"invalid_response\",\n message: `Unexpected HTTP ${response.status} from Billete`,\n requestId: response.headers.get(\"X-Request-Id\") ?? undefined,\n },\n response.status,\n );\n}\n\nexport type Transport = ReturnType<typeof createTransport>;\n\nexport function createTransport(options: TransportOptions & { token?: () => string | undefined }) {\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n const fetchImpl =\n options.fetch ?? ((...args: Parameters<typeof fetch>) => globalThis.fetch(...args));\n const timeoutMs = options.timeoutMs ?? 30_000;\n const maxRetries = options.maxRetries ?? 2;\n\n async function send<T>(\n request: Request,\n idempotencyKey: string | undefined,\n attempt: number,\n ): Promise<T> {\n const token = options.token?.();\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"X-Billete-Client\": `billete-js/${SDK_VERSION}`,\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n ...(request.body === undefined ? {} : { \"Content-Type\": \"application/json\" }),\n ...(idempotencyKey ? { \"Idempotency-Key\": idempotencyKey } : {}),\n };\n const signals = [AbortSignal.timeout(timeoutMs), request.options?.signal].filter(\n (signal): signal is AbortSignal => signal !== undefined,\n );\n const response = await fetchImpl(buildUrl(baseUrl, request.path, request.query), {\n method: request.method,\n headers,\n body: request.body === undefined ? undefined : JSON.stringify(request.body),\n signal: AbortSignal.any(signals),\n }).catch((error: unknown) => {\n if (request.options?.signal?.aborted) throw error;\n return error instanceof Error ? error : new Error(String(error));\n });\n\n const retryable =\n response instanceof Error ||\n RETRYABLE_STATUS.has(response.status) ||\n (response.status === 409 && (await inProgress(response)));\n if (retryable && attempt < maxRetries) {\n await pause(\n backoff(attempt, response instanceof Error ? null : response.headers.get(\"Retry-After\")),\n request.options?.signal,\n );\n return send(request, idempotencyKey, attempt + 1);\n }\n if (response instanceof Error) {\n throw new BilleteError(\n {\n type: \"network\",\n code: \"network_error\",\n message: `Could not reach Billete: ${response.message}`,\n },\n 0,\n );\n }\n if (!response.ok) throw await toError(response);\n return (response.status === 204 ? undefined : await response.json()) as T;\n }\n\n function request<T>(request: Request): Promise<T> {\n const idempotencyKey =\n request.method === \"GET\"\n ? undefined\n : (request.options?.idempotencyKey ?? globalThis.crypto.randomUUID());\n return send<T>(request, idempotencyKey, 0);\n }\n\n /** Walks every page of a list endpoint. */\n async function* paginate<T>(path: string, query: Query = {}): AsyncGenerator<T> {\n const page = await request<List<T>>({ method: \"GET\", path, query });\n yield* page.data;\n if (page.nextCursor) yield* paginate<T>(path, { ...query, cursor: page.nextCursor });\n }\n\n return { request, paginate };\n}\n\n/** Encodes a path segment (customer references may be merchant-defined external IDs). */\nexport function segment(value: string) {\n return encodeURIComponent(value);\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { $ as Subscription, A as Payment, At as ENVIRONMENTS, B as Product, C as FEATURE_TYPES, Ct as MobilePrefix, D as FeatureUsageResult, Dt as isLandlinePhone, E as FeatureType, Et as Timestamp, F as PaymentStatus, G as RISK_LEVELS, H as ProductVariant, I as Payout, J as Retirement, K as Refund, L as Plan, M as PaymentFailure, Mt as TokenKind, N as PaymentLink, Nt as tokenEnvironment, O as PAYMENT_STATUSES, Ot as isMobilePhone, P as PaymentSettlement, Pt as tokenPrefix, Q as SubmitLabel, R as PlanFeatureValues, S as ExchangeRate, St as Metadata, T as FeatureReset, Tt as TaxBehavior, U as QuantitySettings, V as ProductCategory, W as RESET_FREQUENCIES, X as SUBMIT_LABELS, Y as RiskLevel, Z as SUBSCRIPTION_STATUSES, _ as Customer, _t as GRACE_PERIOD_DAYS, a as Actor, at as API_ERROR_TYPES, b as CustomerSession, bt as ListQuery, c as BalanceAmount, ct as ApiErrorType, d as CheckoutItem, dt as Address, et as SubscriptionRenewal, f as CheckoutPurpose, ft as AddressInput, g as CustomField, gt as Currency, h as CurrencyAvailability, ht as CURRENCIES, i as WebhookEvent, it as Weekday, j as PaymentAttempt, jt as Environment, k as PRODUCT_CATEGORIES, kt as nationalPhone, l as CHECKOUT_PURPOSES, lt as PAYMENT_FAILURE_CODES, m as Coupon, mt as BillingMonths, n as EventDataMap, nt as USAGE_FEATURE_TYPES, o as AfterCompletion, ot as ApiErrorBody, p as CollectSettings, pt as BILLING_MONTHS, q as ResetFrequency, r as EventType, rt as WebhookEndpoint, s as Balance, st as ApiErrorEnvelope, t as EVENT_TYPES, tt as SubscriptionStatus, u as Checkout, ut as PaymentFailureCode, v as CustomerDetails, vt as GracePeriodDays, w as Feature, wt as TAX_BEHAVIORS, x as Event, xt as MOBILE_PREFIXES, y as CustomerFeature, yt as List, z as PlanPrice } from "./events-BJuSt_4W.js";
|
|
2
|
+
import { A as ProductCheckoutCreate, B as VariantInput, C as PaymentLinkUpdate, D as PlanLinkCreate, E as PlanFeaturesInput, F as RefundCreate, G as PaymentMethodField, H as WebhookEndpointUpdate, I as RetireInput, J as PublicPaymentLink, K as PaymentTestScenario, L as SubscriptionCancel, M as ProductLinkCreate, N as ProductUpdate, O as PlanPriceInput, P as QuantityInput, R as SubscriptionCreate, S as PaymentLinkCreate, T as PlanCreate, U as CheckoutPaymentMethod, V as WebhookEndpointCreate, W as PaymentInstructions, _ as CustomerUpdate, a as CheckoutCreate, b as FeatureUpdate, c as ClientCheckoutUpdate, d as CouponCreate, f as CouponUpdate, g as CustomerSessionCreate, h as CustomerInput, i as AfterCompletionInput, j as ProductCreate, k as PlanUpdate, l as ClientLinkCheckout, m as CustomerCreate, n as RequestOptions, o as CheckoutItemInput, p as CustomFieldInput, q as PublicCheckout, r as TransportOptions, s as ClientCheckoutPay, t as BilleteError, u as CollectInput, v as EventSelector, w as PlanCheckoutCreate, x as KEY_PATTERN, y as FeatureCreate, z as SubscriptionUpdate } from "./errors-oT7VMFhd.js";
|
|
3
|
+
import { WebhookVerificationError, verifyWebhook } from "./webhooks.js";
|
|
4
|
+
//#region src/server.d.ts
|
|
5
|
+
type Filters<T> = ListQuery & T;
|
|
6
|
+
interface BilleteOptions extends TransportOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Where this client operates: `test` or `production`. Required, and it must match the API key
|
|
9
|
+
* (`sk_test_…` / `sk_prod_…`), so a key never runs in an environment it was not meant for.
|
|
10
|
+
*/
|
|
11
|
+
environment: Environment;
|
|
12
|
+
/** Defaults to the `BILLETE_API_KEY` environment variable. Never ship it to a browser. */
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/** API keys must never reach a browser. Set only for trusted desktop/server-like runtimes. */
|
|
15
|
+
dangerouslyAllowBrowser?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Server-side Billete client. Works in any runtime with `fetch` (Node, Bun, Deno, edge).
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const billete = createBillete({ environment: "test" }); // reads BILLETE_API_KEY
|
|
22
|
+
* const { allowed } = await billete.features.consume("user_123", "tokens", 850);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function createBillete(options: BilleteOptions): {
|
|
26
|
+
customers: {
|
|
27
|
+
create: (input: CustomerCreate, options?: RequestOptions) => Promise<Customer>;
|
|
28
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Customer>;
|
|
29
|
+
update: (id: string, input: CustomerUpdate, options?: RequestOptions) => Promise<Customer>;
|
|
30
|
+
list: (query?: Filters<{
|
|
31
|
+
email?: string;
|
|
32
|
+
}>, options?: RequestOptions) => Promise<List<Customer>>;
|
|
33
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
34
|
+
iterate: (query?: Filters<{
|
|
35
|
+
email?: string;
|
|
36
|
+
}>) => AsyncGenerator<Customer, any, any>;
|
|
37
|
+
};
|
|
38
|
+
customerSessions: {
|
|
39
|
+
/** Short-lived token for the client SDK to read one customer's billing state. */
|
|
40
|
+
create: (input: CustomerSessionCreate, options?: RequestOptions) => Promise<CustomerSession>;
|
|
41
|
+
};
|
|
42
|
+
products: {
|
|
43
|
+
create: (input: ProductCreate, options?: RequestOptions) => Promise<Product>;
|
|
44
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Product>;
|
|
45
|
+
update: (id: string, input: ProductUpdate, options?: RequestOptions) => Promise<Product>;
|
|
46
|
+
list: (query?: Filters<{
|
|
47
|
+
active?: boolean;
|
|
48
|
+
retired?: boolean;
|
|
49
|
+
}>, options?: RequestOptions) => Promise<List<Product>>;
|
|
50
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
51
|
+
iterate: (query?: Filters<{
|
|
52
|
+
active?: boolean;
|
|
53
|
+
retired?: boolean;
|
|
54
|
+
}>) => AsyncGenerator<Product, any, any>;
|
|
55
|
+
retire: (id: string, input?: RetireInput, options?: RequestOptions) => Promise<Product>;
|
|
56
|
+
retireVariant: (product: string, variant: string, input?: RetireInput, options?: RequestOptions) => Promise<Product>;
|
|
57
|
+
};
|
|
58
|
+
plans: {
|
|
59
|
+
create: (input: PlanCreate, options?: RequestOptions) => Promise<Plan>;
|
|
60
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Plan>;
|
|
61
|
+
update: (id: string, input: PlanUpdate, options?: RequestOptions) => Promise<Plan>;
|
|
62
|
+
list: (query?: Filters<{
|
|
63
|
+
active?: boolean;
|
|
64
|
+
}>, options?: RequestOptions) => Promise<List<Plan>>;
|
|
65
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
66
|
+
iterate: (query?: Filters<{
|
|
67
|
+
active?: boolean;
|
|
68
|
+
}>) => AsyncGenerator<Plan, any, any>;
|
|
69
|
+
};
|
|
70
|
+
features: {
|
|
71
|
+
create: (input: FeatureCreate, options?: RequestOptions) => Promise<Feature>;
|
|
72
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Feature>;
|
|
73
|
+
update: (id: string, input: FeatureUpdate, options?: RequestOptions) => Promise<Feature>;
|
|
74
|
+
list: (query?: Filters<object>, options?: RequestOptions) => Promise<List<Feature>>;
|
|
75
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
76
|
+
iterate: (query?: Filters<object>) => AsyncGenerator<Feature, any, any>;
|
|
77
|
+
/** Current access to one feature. `customer` is a `cus_…` ID or your own `externalId`. */
|
|
78
|
+
check: (customer: string, key: string, options?: RequestOptions) => Promise<CustomerFeature>;
|
|
79
|
+
/** `true` when the customer can use a boolean feature (or has a limit feature). */
|
|
80
|
+
isEnabled: (customer: string, key: string, options?: RequestOptions) => Promise<boolean>;
|
|
81
|
+
/** Every feature and its usage for one customer. */
|
|
82
|
+
listForCustomer: (customer: string, options?: RequestOptions) => Promise<List<CustomerFeature>>;
|
|
83
|
+
/**
|
|
84
|
+
* Atomically consumes `amount` units. When the limit would be exceeded nothing is consumed
|
|
85
|
+
* and `allowed` is false, so it is safe to call right before doing the work.
|
|
86
|
+
*/
|
|
87
|
+
consume: (customer: string, key: string, amount?: number, options?: RequestOptions) => Promise<FeatureUsageResult>;
|
|
88
|
+
/** Gives back `amount` units (e.g. a folder was deleted). Usage never drops below zero. */
|
|
89
|
+
release: (customer: string, key: string, amount?: number, options?: RequestOptions) => Promise<FeatureUsageResult>;
|
|
90
|
+
/** Reports the absolute current usage (e.g. the customer now has 7 folders). */
|
|
91
|
+
set: (customer: string, key: string, value: number, options?: RequestOptions) => Promise<CustomerFeature>;
|
|
92
|
+
};
|
|
93
|
+
coupons: {
|
|
94
|
+
create: (input: CouponCreate, options?: RequestOptions) => Promise<Coupon>;
|
|
95
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Coupon>;
|
|
96
|
+
update: (id: string, input: CouponUpdate, options?: RequestOptions) => Promise<Coupon>;
|
|
97
|
+
list: (query?: Filters<object>, options?: RequestOptions) => Promise<List<Coupon>>;
|
|
98
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
99
|
+
iterate: (query?: Filters<object>) => AsyncGenerator<Coupon, any, any>;
|
|
100
|
+
};
|
|
101
|
+
checkouts: {
|
|
102
|
+
create: (input: CheckoutCreate, options?: RequestOptions) => Promise<Checkout>;
|
|
103
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Checkout>;
|
|
104
|
+
list: (query?: Filters<object>, options?: RequestOptions) => Promise<List<Checkout>>;
|
|
105
|
+
iterate: (query?: Filters<object>) => AsyncGenerator<Checkout, any, any>;
|
|
106
|
+
expire: (id: string, options?: RequestOptions) => Promise<Checkout>;
|
|
107
|
+
};
|
|
108
|
+
paymentLinks: {
|
|
109
|
+
create: (input: PaymentLinkCreate, options?: RequestOptions) => Promise<PaymentLink>;
|
|
110
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<PaymentLink>;
|
|
111
|
+
update: (id: string, input: PaymentLinkUpdate, options?: RequestOptions) => Promise<PaymentLink>;
|
|
112
|
+
list: (query?: Filters<{
|
|
113
|
+
retired?: boolean;
|
|
114
|
+
}>, options?: RequestOptions) => Promise<List<PaymentLink>>;
|
|
115
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
116
|
+
iterate: (query?: Filters<{
|
|
117
|
+
retired?: boolean;
|
|
118
|
+
}>) => AsyncGenerator<PaymentLink, any, any>;
|
|
119
|
+
retire: (id: string, input?: RetireInput, options?: RequestOptions) => Promise<PaymentLink>;
|
|
120
|
+
};
|
|
121
|
+
subscriptions: {
|
|
122
|
+
create: (input: SubscriptionCreate, options?: RequestOptions) => Promise<Subscription>;
|
|
123
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Subscription>;
|
|
124
|
+
update: (id: string, input: SubscriptionUpdate, options?: RequestOptions) => Promise<Subscription>;
|
|
125
|
+
list: (query?: Filters<{
|
|
126
|
+
customer?: string;
|
|
127
|
+
plan?: string;
|
|
128
|
+
status?: SubscriptionStatus;
|
|
129
|
+
}>, options?: RequestOptions) => Promise<List<Subscription>>;
|
|
130
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
131
|
+
iterate: (query?: Filters<{
|
|
132
|
+
customer?: string;
|
|
133
|
+
plan?: string;
|
|
134
|
+
status?: SubscriptionStatus;
|
|
135
|
+
}>) => AsyncGenerator<Subscription, any, any>;
|
|
136
|
+
/** Cancels at period end by default; pass `{ atPeriodEnd: false }` to end it now. */
|
|
137
|
+
cancel: (id: string, input?: SubscriptionCancel, options?: RequestOptions) => Promise<Subscription>;
|
|
138
|
+
resume: (id: string, options?: RequestOptions) => Promise<Subscription>;
|
|
139
|
+
};
|
|
140
|
+
payments: {
|
|
141
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Payment>;
|
|
142
|
+
list: (query?: Filters<{
|
|
143
|
+
customer?: string;
|
|
144
|
+
status?: PaymentStatus;
|
|
145
|
+
}>, options?: RequestOptions) => Promise<List<Payment>>;
|
|
146
|
+
iterate: (query?: Filters<{
|
|
147
|
+
customer?: string;
|
|
148
|
+
status?: PaymentStatus;
|
|
149
|
+
}>) => AsyncGenerator<Payment, any, any>;
|
|
150
|
+
refund: (id: string, input?: RefundCreate, options?: RequestOptions) => Promise<Refund>;
|
|
151
|
+
listRefunds: (id: string, options?: RequestOptions) => Promise<List<Refund>>;
|
|
152
|
+
};
|
|
153
|
+
balance: {
|
|
154
|
+
retrieve: (options?: RequestOptions) => Promise<Balance>;
|
|
155
|
+
};
|
|
156
|
+
payouts: {
|
|
157
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Payout>;
|
|
158
|
+
list: (query?: Filters<object>, options?: RequestOptions) => Promise<List<Payout>>;
|
|
159
|
+
iterate: (query?: Filters<object>) => AsyncGenerator<Payout, any, any>;
|
|
160
|
+
};
|
|
161
|
+
webhookEndpoints: {
|
|
162
|
+
create: (input: WebhookEndpointCreate, options?: RequestOptions) => Promise<WebhookEndpoint>;
|
|
163
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<WebhookEndpoint>;
|
|
164
|
+
update: (id: string, input: WebhookEndpointUpdate, options?: RequestOptions) => Promise<WebhookEndpoint>;
|
|
165
|
+
list: (query?: Filters<object>, options?: RequestOptions) => Promise<List<WebhookEndpoint>>;
|
|
166
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
167
|
+
iterate: (query?: Filters<object>) => AsyncGenerator<WebhookEndpoint, any, any>;
|
|
168
|
+
delete: (id: string, options?: RequestOptions) => Promise<void>;
|
|
169
|
+
};
|
|
170
|
+
events: {
|
|
171
|
+
retrieve: (id: string, options?: RequestOptions) => Promise<Event<Record<string, unknown>>>;
|
|
172
|
+
list: (query?: Filters<{
|
|
173
|
+
type?: EventType;
|
|
174
|
+
}>, options?: RequestOptions) => Promise<List<Event<Record<string, unknown>>>>;
|
|
175
|
+
iterate: (query?: Filters<{
|
|
176
|
+
type?: EventType;
|
|
177
|
+
}>) => AsyncGenerator<Event<Record<string, unknown>>, any, any>;
|
|
178
|
+
};
|
|
179
|
+
exchangeRates: {
|
|
180
|
+
/** Current official BCV rates (USD/VES, EUR/VES). */
|
|
181
|
+
list: (options?: RequestOptions) => Promise<List<ExchangeRate>>;
|
|
182
|
+
};
|
|
183
|
+
currencies: {
|
|
184
|
+
list: (options?: RequestOptions) => Promise<List<CurrencyAvailability>>;
|
|
185
|
+
};
|
|
186
|
+
webhooks: {
|
|
187
|
+
/**
|
|
188
|
+
* Verifies a webhook (Standard Webhooks signature) and returns the typed event.
|
|
189
|
+
* Pass the raw request body exactly as received.
|
|
190
|
+
*/
|
|
191
|
+
verify: (payload: string, headers: Headers | Record<string, string | string[] | undefined>, secret: string) => Promise<WebhookEvent>;
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
/** The server client: what `createBillete` returns. */
|
|
195
|
+
type Billete = ReturnType<typeof createBillete>;
|
|
196
|
+
//#endregion
|
|
197
|
+
export { type API_ERROR_TYPES, type Actor, type Address, type AddressInput, type AfterCompletion, type AfterCompletionInput, type ApiErrorBody, type ApiErrorEnvelope, type ApiErrorType, type BILLING_MONTHS, type Balance, type BalanceAmount, type Billete, BilleteError, type BilleteOptions, type BillingMonths, type CHECKOUT_PURPOSES, type CURRENCIES, type Checkout, type CheckoutCreate, type CheckoutItem, type CheckoutItemInput, type CheckoutPaymentMethod, type CheckoutPurpose, type ClientCheckoutPay, type ClientCheckoutUpdate, type ClientLinkCheckout, type CollectInput, type CollectSettings, type Coupon, type CouponCreate, type CouponUpdate, type Currency, type CurrencyAvailability, type CustomField, type CustomFieldInput, type Customer, type CustomerCreate, type CustomerDetails, type CustomerFeature, type CustomerInput, type CustomerSession, type CustomerSessionCreate, type CustomerUpdate, type ENVIRONMENTS, type EVENT_TYPES, type Environment, type Event, type EventDataMap, type EventSelector, type EventType, type ExchangeRate, type FEATURE_TYPES, type Feature, type FeatureCreate, type FeatureReset, type FeatureType, type FeatureUpdate, type FeatureUsageResult, type GRACE_PERIOD_DAYS, type GracePeriodDays, type KEY_PATTERN, type List, type ListQuery, type MOBILE_PREFIXES, type Metadata, type MobilePrefix, type PAYMENT_FAILURE_CODES, type PAYMENT_STATUSES, type PRODUCT_CATEGORIES, type Payment, type PaymentAttempt, type PaymentFailure, type PaymentFailureCode, type PaymentInstructions, type PaymentLink, type PaymentLinkCreate, type PaymentLinkUpdate, type PaymentMethodField, type PaymentSettlement, type PaymentStatus, type PaymentTestScenario, type Payout, type Plan, type PlanCheckoutCreate, type PlanCreate, type PlanFeatureValues, type PlanFeaturesInput, type PlanLinkCreate, type PlanPrice, type PlanPriceInput, type PlanUpdate, type Product, type ProductCategory, type ProductCheckoutCreate, type ProductCreate, type ProductLinkCreate, type ProductUpdate, type ProductVariant, type PublicCheckout, type PublicPaymentLink, type QuantityInput, type QuantitySettings, type RESET_FREQUENCIES, type RISK_LEVELS, type Refund, type RefundCreate, type RequestOptions, type ResetFrequency, type RetireInput, type Retirement, type RiskLevel, type SUBMIT_LABELS, type SUBSCRIPTION_STATUSES, type SubmitLabel, type Subscription, type SubscriptionCancel, type SubscriptionCreate, type SubscriptionRenewal, type SubscriptionStatus, type SubscriptionUpdate, type TAX_BEHAVIORS, type TaxBehavior, type Timestamp, type TokenKind, type TransportOptions, type USAGE_FEATURE_TYPES, type VariantInput, type WebhookEndpoint, type WebhookEndpointCreate, type WebhookEndpointUpdate, type WebhookEvent, WebhookVerificationError, type Weekday, type isLandlinePhone, type isMobilePhone, type nationalPhone, type tokenEnvironment, type tokenPrefix, verifyWebhook };
|
|
198
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { n as segment, r as BilleteError, t as createTransport } from "./http-ChElV20o.js";
|
|
2
|
+
import { WebhookVerificationError, verifyWebhook } from "./webhooks.js";
|
|
3
|
+
//#region ../contracts/src/environment.ts
|
|
4
|
+
/**
|
|
5
|
+
* Environments and the credentials that name them. Kept free of runtime dependencies: the public
|
|
6
|
+
* SDK bundles this module to check API keys.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Where an account operates. `test` is always available and moves no money; `production`
|
|
10
|
+
* charges for real. Every resource carries it, and secret keys name it: `sk_test_`, `sk_prod_`.
|
|
11
|
+
*/
|
|
12
|
+
const ENVIRONMENTS = ["test", "production"];
|
|
13
|
+
/** How tokens spell each environment: `sk_test_…`, `sk_prod_…`. */
|
|
14
|
+
const TOKEN_ENVIRONMENT_CODES = {
|
|
15
|
+
test: "test",
|
|
16
|
+
production: "prod"
|
|
17
|
+
};
|
|
18
|
+
/** The prefix of a token for an environment, e.g. `sk_prod_`. */
|
|
19
|
+
function tokenPrefix(kind, environment) {
|
|
20
|
+
return `${kind}_${TOKEN_ENVIRONMENT_CODES[environment]}_`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The environment a token belongs to by its prefix, or `null` when it is not that kind of token.
|
|
24
|
+
*/
|
|
25
|
+
function tokenEnvironment(kind, token) {
|
|
26
|
+
return ENVIRONMENTS.find((environment) => token.startsWith(tokenPrefix(kind, environment))) ?? null;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/server.ts
|
|
30
|
+
function collection(http, path) {
|
|
31
|
+
return {
|
|
32
|
+
create: (input, options) => http.request({
|
|
33
|
+
method: "POST",
|
|
34
|
+
path,
|
|
35
|
+
body: input,
|
|
36
|
+
options
|
|
37
|
+
}),
|
|
38
|
+
retrieve: (id, options) => http.request({
|
|
39
|
+
method: "GET",
|
|
40
|
+
path: `${path}/${segment(id)}`,
|
|
41
|
+
options
|
|
42
|
+
}),
|
|
43
|
+
update: (id, input, options) => http.request({
|
|
44
|
+
method: "PATCH",
|
|
45
|
+
path: `${path}/${segment(id)}`,
|
|
46
|
+
body: input,
|
|
47
|
+
options
|
|
48
|
+
}),
|
|
49
|
+
list: (query = {}, options) => http.request({
|
|
50
|
+
method: "GET",
|
|
51
|
+
path,
|
|
52
|
+
query,
|
|
53
|
+
options
|
|
54
|
+
}),
|
|
55
|
+
/** Iterates every item across pages: `for await (const item of iterate())`. */
|
|
56
|
+
iterate: (query = {}) => http.paginate(path, { ...query })
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function fromEnv(name) {
|
|
60
|
+
return globalThis.process?.env?.[name];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Server-side Billete client. Works in any runtime with `fetch` (Node, Bun, Deno, edge).
|
|
64
|
+
*
|
|
65
|
+
* ```ts
|
|
66
|
+
* const billete = createBillete({ environment: "test" }); // reads BILLETE_API_KEY
|
|
67
|
+
* const { allowed } = await billete.features.consume("user_123", "tokens", 850);
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
function createBillete(options) {
|
|
71
|
+
const apiKey = options.apiKey ?? fromEnv("BILLETE_API_KEY");
|
|
72
|
+
if (!apiKey) throw new TypeError("Billete needs an API key: pass apiKey or set BILLETE_API_KEY");
|
|
73
|
+
const keyEnvironment = tokenEnvironment("sk", apiKey);
|
|
74
|
+
if (!keyEnvironment) throw new TypeError("Billete expects an API key (sk_test_… or sk_prod_…)");
|
|
75
|
+
if (keyEnvironment !== options.environment) throw new TypeError(`This API key belongs to the ${keyEnvironment} environment, but environment is "${options.environment}"`);
|
|
76
|
+
if (typeof window !== "undefined" && !options.dangerouslyAllowBrowser) throw new Error("Never use an API key in the browser. Use createClient from @boelabs/billete-sdk/client instead.");
|
|
77
|
+
const http = createTransport({
|
|
78
|
+
...options,
|
|
79
|
+
baseUrl: options.baseUrl ?? fromEnv("BILLETE_BASE_URL"),
|
|
80
|
+
token: () => apiKey
|
|
81
|
+
});
|
|
82
|
+
const customers = collection(http, "/v1/customers");
|
|
83
|
+
const customerSessions = {
|
|
84
|
+
/** Short-lived token for the client SDK to read one customer's billing state. */
|
|
85
|
+
create: (input, options) => http.request({
|
|
86
|
+
method: "POST",
|
|
87
|
+
path: "/v1/customer-sessions",
|
|
88
|
+
body: input,
|
|
89
|
+
options
|
|
90
|
+
}) };
|
|
91
|
+
const products = {
|
|
92
|
+
...collection(http, "/v1/products"),
|
|
93
|
+
retire: (id, input = {}, options) => http.request({
|
|
94
|
+
method: "POST",
|
|
95
|
+
path: `/v1/products/${segment(id)}/retire`,
|
|
96
|
+
body: input,
|
|
97
|
+
options
|
|
98
|
+
}),
|
|
99
|
+
retireVariant: (product, variant, input = {}, options) => http.request({
|
|
100
|
+
method: "POST",
|
|
101
|
+
path: `/v1/products/${segment(product)}/variants/${segment(variant)}/retire`,
|
|
102
|
+
body: input,
|
|
103
|
+
options
|
|
104
|
+
})
|
|
105
|
+
};
|
|
106
|
+
const plans = collection(http, "/v1/plans");
|
|
107
|
+
const coupons = collection(http, "/v1/coupons");
|
|
108
|
+
const paymentLinks = {
|
|
109
|
+
...collection(http, "/v1/payment-links"),
|
|
110
|
+
retire: (id, input = {}, options) => http.request({
|
|
111
|
+
method: "POST",
|
|
112
|
+
path: `/v1/payment-links/${segment(id)}/retire`,
|
|
113
|
+
body: input,
|
|
114
|
+
options
|
|
115
|
+
})
|
|
116
|
+
};
|
|
117
|
+
const customerFeature = (customer, key) => `/v1/customers/${segment(customer)}/features/${segment(key)}`;
|
|
118
|
+
const features = {
|
|
119
|
+
...collection(http, "/v1/features"),
|
|
120
|
+
/** Current access to one feature. `customer` is a `cus_…` ID or your own `externalId`. */
|
|
121
|
+
check: (customer, key, options) => http.request({
|
|
122
|
+
method: "GET",
|
|
123
|
+
path: customerFeature(customer, key),
|
|
124
|
+
options
|
|
125
|
+
}),
|
|
126
|
+
/** `true` when the customer can use a boolean feature (or has a limit feature). */
|
|
127
|
+
isEnabled: async (customer, key, options) => (await http.request({
|
|
128
|
+
method: "GET",
|
|
129
|
+
path: customerFeature(customer, key),
|
|
130
|
+
options
|
|
131
|
+
})).enabled,
|
|
132
|
+
/** Every feature and its usage for one customer. */
|
|
133
|
+
listForCustomer: (customer, options) => http.request({
|
|
134
|
+
method: "GET",
|
|
135
|
+
path: `/v1/customers/${segment(customer)}/features`,
|
|
136
|
+
options
|
|
137
|
+
}),
|
|
138
|
+
/**
|
|
139
|
+
* Atomically consumes `amount` units. When the limit would be exceeded nothing is consumed
|
|
140
|
+
* and `allowed` is false, so it is safe to call right before doing the work.
|
|
141
|
+
*/
|
|
142
|
+
consume: (customer, key, amount = 1, options) => http.request({
|
|
143
|
+
method: "POST",
|
|
144
|
+
path: `${customerFeature(customer, key)}/usage`,
|
|
145
|
+
body: { delta: amount },
|
|
146
|
+
options
|
|
147
|
+
}),
|
|
148
|
+
/** Gives back `amount` units (e.g. a folder was deleted). Usage never drops below zero. */
|
|
149
|
+
release: (customer, key, amount = 1, options) => http.request({
|
|
150
|
+
method: "POST",
|
|
151
|
+
path: `${customerFeature(customer, key)}/usage`,
|
|
152
|
+
body: { delta: -amount },
|
|
153
|
+
options
|
|
154
|
+
}),
|
|
155
|
+
/** Reports the absolute current usage (e.g. the customer now has 7 folders). */
|
|
156
|
+
set: (customer, key, value, options) => http.request({
|
|
157
|
+
method: "PUT",
|
|
158
|
+
path: `${customerFeature(customer, key)}/usage`,
|
|
159
|
+
body: { value },
|
|
160
|
+
options
|
|
161
|
+
})
|
|
162
|
+
};
|
|
163
|
+
const checkoutCrud = collection(http, "/v1/checkouts");
|
|
164
|
+
const checkouts = {
|
|
165
|
+
create: checkoutCrud.create,
|
|
166
|
+
retrieve: checkoutCrud.retrieve,
|
|
167
|
+
list: checkoutCrud.list,
|
|
168
|
+
iterate: checkoutCrud.iterate,
|
|
169
|
+
expire: (id, options) => http.request({
|
|
170
|
+
method: "POST",
|
|
171
|
+
path: `/v1/checkouts/${segment(id)}/expire`,
|
|
172
|
+
options
|
|
173
|
+
})
|
|
174
|
+
};
|
|
175
|
+
const subscriptions = {
|
|
176
|
+
...collection(http, "/v1/subscriptions"),
|
|
177
|
+
/** Cancels at period end by default; pass `{ atPeriodEnd: false }` to end it now. */
|
|
178
|
+
cancel: (id, input = {}, options) => http.request({
|
|
179
|
+
method: "POST",
|
|
180
|
+
path: `/v1/subscriptions/${segment(id)}/cancel`,
|
|
181
|
+
body: input,
|
|
182
|
+
options
|
|
183
|
+
}),
|
|
184
|
+
resume: (id, options) => http.request({
|
|
185
|
+
method: "POST",
|
|
186
|
+
path: `/v1/subscriptions/${segment(id)}/resume`,
|
|
187
|
+
options
|
|
188
|
+
})
|
|
189
|
+
};
|
|
190
|
+
const paymentCrud = collection(http, "/v1/payments");
|
|
191
|
+
const payments = {
|
|
192
|
+
retrieve: paymentCrud.retrieve,
|
|
193
|
+
list: paymentCrud.list,
|
|
194
|
+
iterate: paymentCrud.iterate,
|
|
195
|
+
refund: (id, input = {}, options) => http.request({
|
|
196
|
+
method: "POST",
|
|
197
|
+
path: `/v1/payments/${segment(id)}/refunds`,
|
|
198
|
+
body: input,
|
|
199
|
+
options
|
|
200
|
+
}),
|
|
201
|
+
listRefunds: (id, options) => http.request({
|
|
202
|
+
method: "GET",
|
|
203
|
+
path: `/v1/payments/${segment(id)}/refunds`,
|
|
204
|
+
options
|
|
205
|
+
})
|
|
206
|
+
};
|
|
207
|
+
const balance = { retrieve: (options) => http.request({
|
|
208
|
+
method: "GET",
|
|
209
|
+
path: "/v1/balance",
|
|
210
|
+
options
|
|
211
|
+
}) };
|
|
212
|
+
const payoutCrud = collection(http, "/v1/payouts");
|
|
213
|
+
const payouts = {
|
|
214
|
+
retrieve: payoutCrud.retrieve,
|
|
215
|
+
list: payoutCrud.list,
|
|
216
|
+
iterate: payoutCrud.iterate
|
|
217
|
+
};
|
|
218
|
+
const webhookEndpoints = {
|
|
219
|
+
...collection(http, "/v1/webhook-endpoints"),
|
|
220
|
+
delete: (id, options) => http.request({
|
|
221
|
+
method: "DELETE",
|
|
222
|
+
path: `/v1/webhook-endpoints/${segment(id)}`,
|
|
223
|
+
options
|
|
224
|
+
})
|
|
225
|
+
};
|
|
226
|
+
const eventCrud = collection(http, "/v1/events");
|
|
227
|
+
return {
|
|
228
|
+
customers,
|
|
229
|
+
customerSessions,
|
|
230
|
+
products,
|
|
231
|
+
plans,
|
|
232
|
+
features,
|
|
233
|
+
coupons,
|
|
234
|
+
checkouts,
|
|
235
|
+
paymentLinks,
|
|
236
|
+
subscriptions,
|
|
237
|
+
payments,
|
|
238
|
+
balance,
|
|
239
|
+
payouts,
|
|
240
|
+
webhookEndpoints,
|
|
241
|
+
events: {
|
|
242
|
+
retrieve: eventCrud.retrieve,
|
|
243
|
+
list: eventCrud.list,
|
|
244
|
+
iterate: eventCrud.iterate
|
|
245
|
+
},
|
|
246
|
+
exchangeRates: {
|
|
247
|
+
/** Current official BCV rates (USD/VES, EUR/VES). */
|
|
248
|
+
list: (options) => http.request({
|
|
249
|
+
method: "GET",
|
|
250
|
+
path: "/v1/exchange-rates",
|
|
251
|
+
options
|
|
252
|
+
}) },
|
|
253
|
+
currencies: { list: (options) => http.request({
|
|
254
|
+
method: "GET",
|
|
255
|
+
path: "/v1/currencies",
|
|
256
|
+
options
|
|
257
|
+
}) },
|
|
258
|
+
webhooks: {
|
|
259
|
+
/**
|
|
260
|
+
* Verifies a webhook (Standard Webhooks signature) and returns the typed event.
|
|
261
|
+
* Pass the raw request body exactly as received.
|
|
262
|
+
*/
|
|
263
|
+
verify: (payload, headers, secret) => verifyWebhook({
|
|
264
|
+
payload,
|
|
265
|
+
headers,
|
|
266
|
+
secret
|
|
267
|
+
}) }
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
export { BilleteError, WebhookVerificationError, createBillete, verifyWebhook };
|
|
272
|
+
|
|
273
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["runtime"],"sources":["../../contracts/src/environment.ts","../src/server.ts"],"sourcesContent":["/**\n * Environments and the credentials that name them. Kept free of runtime dependencies: the public\n * SDK bundles this module to check API keys.\n */\n\n/**\n * Where an account operates. `test` is always available and moves no money; `production`\n * charges for real. Every resource carries it, and secret keys name it: `sk_test_`, `sk_prod_`.\n */\nexport const ENVIRONMENTS = [\"test\", \"production\"] as const;\nexport type Environment = (typeof ENVIRONMENTS)[number];\n\n/** How tokens spell each environment: `sk_test_…`, `sk_prod_…`. */\nconst TOKEN_ENVIRONMENT_CODES = { test: \"test\", production: \"prod\" } as const satisfies Record<\n Environment,\n string\n>;\n\n/** Credentials that name their environment: secret keys and customer session tokens. */\nexport type TokenKind = \"sk\" | \"cst\";\n\n/** The prefix of a token for an environment, e.g. `sk_prod_`. */\nexport function tokenPrefix(kind: TokenKind, environment: Environment) {\n return `${kind}_${TOKEN_ENVIRONMENT_CODES[environment]}_`;\n}\n\n/**\n * The environment a token belongs to by its prefix, or `null` when it is not that kind of token.\n */\nexport function tokenEnvironment(kind: TokenKind, token: string): Environment | null {\n return (\n ENVIRONMENTS.find((environment) => token.startsWith(tokenPrefix(kind, environment))) ?? null\n );\n}\n","import { tokenEnvironment } from \"@boelabs/billete-contracts/environment\";\nimport type {\n Balance,\n Checkout,\n CheckoutCreate,\n Coupon,\n CouponCreate,\n CouponUpdate,\n CurrencyAvailability,\n Customer,\n CustomerCreate,\n CustomerFeature,\n CustomerSession,\n CustomerSessionCreate,\n CustomerUpdate,\n Environment,\n Event,\n EventType,\n ExchangeRate,\n Feature,\n FeatureCreate,\n FeatureUpdate,\n FeatureUsageResult,\n List,\n ListQuery,\n Payment,\n PaymentLink,\n PaymentLinkCreate,\n PaymentLinkUpdate,\n PaymentStatus,\n Payout,\n Plan,\n PlanCreate,\n PlanUpdate,\n Product,\n ProductCreate,\n ProductUpdate,\n RetireInput,\n Refund,\n RefundCreate,\n Subscription,\n SubscriptionCancel,\n SubscriptionCreate,\n SubscriptionStatus,\n SubscriptionUpdate,\n WebhookEndpoint,\n WebhookEndpointCreate,\n WebhookEndpointUpdate,\n WebhookEvent,\n} from \"@boelabs/billete-contracts/public\";\n\nimport { createTransport, segment } from \"./http\";\nimport type { RequestOptions, TransportOptions } from \"./http\";\nimport { verifyWebhook } from \"./webhooks\";\n\ntype Transport = ReturnType<typeof createTransport>;\ntype Filters<T> = ListQuery & T;\n\nfunction collection<TResource, TCreate, TUpdate, TFilters = object>(http: Transport, path: string) {\n return {\n create: (input: TCreate, options?: RequestOptions) =>\n http.request<TResource>({ method: \"POST\", path, body: input, options }),\n retrieve: (id: string, options?: RequestOptions) =>\n http.request<TResource>({ method: \"GET\", path: `${path}/${segment(id)}`, options }),\n update: (id: string, input: TUpdate, options?: RequestOptions) =>\n http.request<TResource>({\n method: \"PATCH\",\n path: `${path}/${segment(id)}`,\n body: input,\n options,\n }),\n list: (query: Filters<TFilters> = {} as Filters<TFilters>, options?: RequestOptions) =>\n http.request<List<TResource>>({ method: \"GET\", path, query, options }),\n /** Iterates every item across pages: `for await (const item of iterate())`. */\n iterate: (query: Filters<TFilters> = {} as Filters<TFilters>) =>\n http.paginate<TResource>(path, { ...query }),\n };\n}\n\nexport interface BilleteOptions extends TransportOptions {\n /**\n * Where this client operates: `test` or `production`. Required, and it must match the API key\n * (`sk_test_…` / `sk_prod_…`), so a key never runs in an environment it was not meant for.\n */\n environment: Environment;\n /** Defaults to the `BILLETE_API_KEY` environment variable. Never ship it to a browser. */\n apiKey?: string;\n /** API keys must never reach a browser. Set only for trusted desktop/server-like runtimes. */\n dangerouslyAllowBrowser?: boolean;\n}\n\nfunction fromEnv(name: string): string | undefined {\n const runtime = globalThis as { process?: { env?: Record<string, string | undefined> } };\n return runtime.process?.env?.[name];\n}\n\n/**\n * Server-side Billete client. Works in any runtime with `fetch` (Node, Bun, Deno, edge).\n *\n * ```ts\n * const billete = createBillete({ environment: \"test\" }); // reads BILLETE_API_KEY\n * const { allowed } = await billete.features.consume(\"user_123\", \"tokens\", 850);\n * ```\n */\nexport function createBillete(options: BilleteOptions) {\n const apiKey = options.apiKey ?? fromEnv(\"BILLETE_API_KEY\");\n if (!apiKey) {\n throw new TypeError(\"Billete needs an API key: pass apiKey or set BILLETE_API_KEY\");\n }\n const keyEnvironment = tokenEnvironment(\"sk\", apiKey);\n if (!keyEnvironment) {\n throw new TypeError(\"Billete expects an API key (sk_test_… or sk_prod_…)\");\n }\n if (keyEnvironment !== options.environment) {\n throw new TypeError(\n `This API key belongs to the ${keyEnvironment} environment, but environment is \"${options.environment}\"`,\n );\n }\n if (typeof window !== \"undefined\" && !options.dangerouslyAllowBrowser) {\n throw new Error(\n \"Never use an API key in the browser. Use createClient from @boelabs/billete-sdk/client instead.\",\n );\n }\n const http = createTransport({\n ...options,\n baseUrl: options.baseUrl ?? fromEnv(\"BILLETE_BASE_URL\"),\n token: () => apiKey,\n });\n\n const customers = collection<Customer, CustomerCreate, CustomerUpdate, { email?: string }>(\n http,\n \"/v1/customers\",\n );\n\n const customerSessions = {\n /** Short-lived token for the client SDK to read one customer's billing state. */\n create: (input: CustomerSessionCreate, options?: RequestOptions) =>\n http.request<CustomerSession>({\n method: \"POST\",\n path: \"/v1/customer-sessions\",\n body: input,\n options,\n }),\n };\n\n const products = {\n ...collection<Product, ProductCreate, ProductUpdate, { active?: boolean; retired?: boolean }>(\n http,\n \"/v1/products\",\n ),\n retire: (id: string, input: RetireInput = {}, options?: RequestOptions) =>\n http.request<Product>({\n method: \"POST\",\n path: `/v1/products/${segment(id)}/retire`,\n body: input,\n options,\n }),\n retireVariant: (\n product: string,\n variant: string,\n input: RetireInput = {},\n options?: RequestOptions,\n ) =>\n http.request<Product>({\n method: \"POST\",\n path: `/v1/products/${segment(product)}/variants/${segment(variant)}/retire`,\n body: input,\n options,\n }),\n };\n const plans = collection<Plan, PlanCreate, PlanUpdate, { active?: boolean }>(http, \"/v1/plans\");\n const coupons = collection<Coupon, CouponCreate, CouponUpdate>(http, \"/v1/coupons\");\n const paymentLinks = {\n ...collection<PaymentLink, PaymentLinkCreate, PaymentLinkUpdate, { retired?: boolean }>(\n http,\n \"/v1/payment-links\",\n ),\n retire: (id: string, input: RetireInput = {}, options?: RequestOptions) =>\n http.request<PaymentLink>({\n method: \"POST\",\n path: `/v1/payment-links/${segment(id)}/retire`,\n body: input,\n options,\n }),\n };\n\n const customerFeature = (customer: string, key: string) =>\n `/v1/customers/${segment(customer)}/features/${segment(key)}`;\n const featureCrud = collection<Feature, FeatureCreate, FeatureUpdate>(http, \"/v1/features\");\n const features = {\n ...featureCrud,\n /** Current access to one feature. `customer` is a `cus_…` ID or your own `externalId`. */\n check: (customer: string, key: string, options?: RequestOptions) =>\n http.request<CustomerFeature>({\n method: \"GET\",\n path: customerFeature(customer, key),\n options,\n }),\n /** `true` when the customer can use a boolean feature (or has a limit feature). */\n isEnabled: async (customer: string, key: string, options?: RequestOptions) =>\n (\n await http.request<CustomerFeature>({\n method: \"GET\",\n path: customerFeature(customer, key),\n options,\n })\n ).enabled,\n /** Every feature and its usage for one customer. */\n listForCustomer: (customer: string, options?: RequestOptions) =>\n http.request<List<CustomerFeature>>({\n method: \"GET\",\n path: `/v1/customers/${segment(customer)}/features`,\n options,\n }),\n /**\n * Atomically consumes `amount` units. When the limit would be exceeded nothing is consumed\n * and `allowed` is false, so it is safe to call right before doing the work.\n */\n consume: (customer: string, key: string, amount = 1, options?: RequestOptions) =>\n http.request<FeatureUsageResult>({\n method: \"POST\",\n path: `${customerFeature(customer, key)}/usage`,\n body: { delta: amount },\n options,\n }),\n /** Gives back `amount` units (e.g. a folder was deleted). Usage never drops below zero. */\n release: (customer: string, key: string, amount = 1, options?: RequestOptions) =>\n http.request<FeatureUsageResult>({\n method: \"POST\",\n path: `${customerFeature(customer, key)}/usage`,\n body: { delta: -amount },\n options,\n }),\n /** Reports the absolute current usage (e.g. the customer now has 7 folders). */\n set: (customer: string, key: string, value: number, options?: RequestOptions) =>\n http.request<CustomerFeature>({\n method: \"PUT\",\n path: `${customerFeature(customer, key)}/usage`,\n body: { value },\n options,\n }),\n };\n\n const checkoutCrud = collection<Checkout, CheckoutCreate, never>(http, \"/v1/checkouts\");\n const checkouts = {\n create: checkoutCrud.create,\n retrieve: checkoutCrud.retrieve,\n list: checkoutCrud.list,\n iterate: checkoutCrud.iterate,\n expire: (id: string, options?: RequestOptions) =>\n http.request<Checkout>({\n method: \"POST\",\n path: `/v1/checkouts/${segment(id)}/expire`,\n options,\n }),\n };\n\n const subscriptionCrud = collection<\n Subscription,\n SubscriptionCreate,\n SubscriptionUpdate,\n { customer?: string; plan?: string; status?: SubscriptionStatus }\n >(http, \"/v1/subscriptions\");\n const subscriptions = {\n ...subscriptionCrud,\n /** Cancels at period end by default; pass `{ atPeriodEnd: false }` to end it now. */\n cancel: (id: string, input: SubscriptionCancel = {}, options?: RequestOptions) =>\n http.request<Subscription>({\n method: \"POST\",\n path: `/v1/subscriptions/${segment(id)}/cancel`,\n body: input,\n options,\n }),\n resume: (id: string, options?: RequestOptions) =>\n http.request<Subscription>({\n method: \"POST\",\n path: `/v1/subscriptions/${segment(id)}/resume`,\n options,\n }),\n };\n\n const paymentCrud = collection<\n Payment,\n never,\n never,\n { customer?: string; status?: PaymentStatus }\n >(http, \"/v1/payments\");\n const payments = {\n retrieve: paymentCrud.retrieve,\n list: paymentCrud.list,\n iterate: paymentCrud.iterate,\n refund: (id: string, input: RefundCreate = {}, options?: RequestOptions) =>\n http.request<Refund>({\n method: \"POST\",\n path: `/v1/payments/${segment(id)}/refunds`,\n body: input,\n options,\n }),\n listRefunds: (id: string, options?: RequestOptions) =>\n http.request<List<Refund>>({\n method: \"GET\",\n path: `/v1/payments/${segment(id)}/refunds`,\n options,\n }),\n };\n\n const balance = {\n retrieve: (options?: RequestOptions) =>\n http.request<Balance>({ method: \"GET\", path: \"/v1/balance\", options }),\n };\n\n const payoutCrud = collection<Payout, never, never>(http, \"/v1/payouts\");\n const payouts = {\n retrieve: payoutCrud.retrieve,\n list: payoutCrud.list,\n iterate: payoutCrud.iterate,\n };\n\n const endpoints = collection<WebhookEndpoint, WebhookEndpointCreate, WebhookEndpointUpdate>(\n http,\n \"/v1/webhook-endpoints\",\n );\n const webhookEndpoints = {\n ...endpoints,\n delete: (id: string, options?: RequestOptions) =>\n http.request<void>({\n method: \"DELETE\",\n path: `/v1/webhook-endpoints/${segment(id)}`,\n options,\n }),\n };\n\n const eventCrud = collection<Event, never, never, { type?: EventType }>(http, \"/v1/events\");\n const events = { retrieve: eventCrud.retrieve, list: eventCrud.list, iterate: eventCrud.iterate };\n\n const exchangeRates = {\n /** Current official BCV rates (USD/VES, EUR/VES). */\n list: (options?: RequestOptions) =>\n http.request<List<ExchangeRate>>({ method: \"GET\", path: \"/v1/exchange-rates\", options }),\n };\n const currencies = {\n list: (options?: RequestOptions) =>\n http.request<List<CurrencyAvailability>>({\n method: \"GET\",\n path: \"/v1/currencies\",\n options,\n }),\n };\n\n const webhooks = {\n /**\n * Verifies a webhook (Standard Webhooks signature) and returns the typed event.\n * Pass the raw request body exactly as received.\n */\n verify: (\n payload: string,\n headers: Headers | Record<string, string | string[] | undefined>,\n secret: string,\n ): Promise<WebhookEvent> => verifyWebhook({ payload, headers, secret }),\n };\n\n return {\n customers,\n customerSessions,\n products,\n plans,\n features,\n coupons,\n checkouts,\n paymentLinks,\n subscriptions,\n payments,\n balance,\n payouts,\n webhookEndpoints,\n events,\n exchangeRates,\n currencies,\n webhooks,\n };\n}\n\n/** The server client: what `createBillete` returns. */\nexport type Billete = ReturnType<typeof createBillete>;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { i as WebhookEvent } from "./events-BJuSt_4W.js";
|
|
2
|
+
//#region src/webhooks.d.ts
|
|
3
|
+
export declare class WebhookVerificationError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
type HeaderSource = Headers | Record<string, string | string[] | undefined>;
|
|
7
|
+
/**
|
|
8
|
+
* Verifies a Billete webhook using the Standard Webhooks scheme with Web Crypto, so it runs in
|
|
9
|
+
* Node, Bun, Deno, Cloudflare Workers and other edge runtimes.
|
|
10
|
+
*/
|
|
11
|
+
export declare function verifyWebhook(input: {
|
|
12
|
+
payload: string;
|
|
13
|
+
headers: HeaderSource;
|
|
14
|
+
secret: string;
|
|
15
|
+
toleranceSeconds?: number;
|
|
16
|
+
now?: Date;
|
|
17
|
+
}): Promise<WebhookEvent>;
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=webhooks.d.ts.map
|
package/dist/webhooks.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/webhooks.ts
|
|
2
|
+
var WebhookVerificationError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "WebhookVerificationError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
function header(headers, name) {
|
|
9
|
+
if (headers instanceof Headers) return headers.get(name) ?? void 0;
|
|
10
|
+
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name)?.[1];
|
|
11
|
+
return Array.isArray(entry) ? entry[0] : entry;
|
|
12
|
+
}
|
|
13
|
+
function fromBase64(value) {
|
|
14
|
+
return Uint8Array.from(atob(value), (char) => char.charCodeAt(0));
|
|
15
|
+
}
|
|
16
|
+
function toBase64(bytes) {
|
|
17
|
+
return btoa(String.fromCharCode(...new Uint8Array(bytes)));
|
|
18
|
+
}
|
|
19
|
+
function constantTimeEqual(left, right) {
|
|
20
|
+
const length = Math.max(left.length, right.length);
|
|
21
|
+
return Array.from({ length }, (_, index) => left.charCodeAt(index) ^ right.charCodeAt(index)).reduce((accumulator, value) => accumulator | value, left.length ^ right.length) === 0;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Verifies a Billete webhook using the Standard Webhooks scheme with Web Crypto, so it runs in
|
|
25
|
+
* Node, Bun, Deno, Cloudflare Workers and other edge runtimes.
|
|
26
|
+
*/
|
|
27
|
+
async function verifyWebhook(input) {
|
|
28
|
+
const id = header(input.headers, "webhook-id");
|
|
29
|
+
const timestamp = header(input.headers, "webhook-timestamp");
|
|
30
|
+
const signatures = header(input.headers, "webhook-signature");
|
|
31
|
+
if (!id || !timestamp || !signatures) throw new WebhookVerificationError("Missing webhook headers");
|
|
32
|
+
const nowSeconds = Math.floor((input.now ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
33
|
+
const tolerance = input.toleranceSeconds ?? 300;
|
|
34
|
+
if (!/^\d+$/.test(timestamp) || Math.abs(nowSeconds - Number(timestamp)) > tolerance) throw new WebhookVerificationError("Webhook timestamp is outside the tolerance window");
|
|
35
|
+
const key = await globalThis.crypto.subtle.importKey("raw", fromBase64(input.secret.replace(/^whsec_/, "")), {
|
|
36
|
+
name: "HMAC",
|
|
37
|
+
hash: "SHA-256"
|
|
38
|
+
}, false, ["sign"]);
|
|
39
|
+
const expected = toBase64(await globalThis.crypto.subtle.sign("HMAC", key, new TextEncoder().encode(`${id}.${timestamp}.${input.payload}`)));
|
|
40
|
+
if (!signatures.split(" ").some((candidate) => candidate.startsWith("v1,") && constantTimeEqual(candidate.slice(3), expected))) throw new WebhookVerificationError("Invalid webhook signature");
|
|
41
|
+
return JSON.parse(input.payload);
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { WebhookVerificationError, verifyWebhook };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=webhooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhooks.js","names":[],"sources":["../src/webhooks.ts"],"sourcesContent":["import type { WebhookEvent } from \"@boelabs/billete-contracts/public\";\n\nexport class WebhookVerificationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"WebhookVerificationError\";\n }\n}\n\ntype HeaderSource = Headers | Record<string, string | string[] | undefined>;\n\nfunction header(headers: HeaderSource, name: string): string | undefined {\n if (headers instanceof Headers) return headers.get(name) ?? undefined;\n const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name)?.[1];\n return Array.isArray(entry) ? entry[0] : entry;\n}\n\nfunction fromBase64(value: string): Uint8Array<ArrayBuffer> {\n return Uint8Array.from(atob(value), (char) => char.charCodeAt(0));\n}\n\nfunction toBase64(bytes: ArrayBuffer): string {\n return btoa(String.fromCharCode(...new Uint8Array(bytes)));\n}\n\nfunction constantTimeEqual(left: string, right: string): boolean {\n const length = Math.max(left.length, right.length);\n const mismatch = Array.from(\n { length },\n (_, index) => left.charCodeAt(index) ^ right.charCodeAt(index),\n ).reduce((accumulator, value) => accumulator | value, left.length ^ right.length);\n return mismatch === 0;\n}\n\n/**\n * Verifies a Billete webhook using the Standard Webhooks scheme with Web Crypto, so it runs in\n * Node, Bun, Deno, Cloudflare Workers and other edge runtimes.\n */\nexport async function verifyWebhook(input: {\n payload: string;\n headers: HeaderSource;\n secret: string;\n toleranceSeconds?: number;\n now?: Date;\n}): Promise<WebhookEvent> {\n const id = header(input.headers, \"webhook-id\");\n const timestamp = header(input.headers, \"webhook-timestamp\");\n const signatures = header(input.headers, \"webhook-signature\");\n if (!id || !timestamp || !signatures) {\n throw new WebhookVerificationError(\"Missing webhook headers\");\n }\n\n const nowSeconds = Math.floor((input.now ?? new Date()).getTime() / 1_000);\n const tolerance = input.toleranceSeconds ?? 300;\n if (!/^\\d+$/.test(timestamp) || Math.abs(nowSeconds - Number(timestamp)) > tolerance) {\n throw new WebhookVerificationError(\"Webhook timestamp is outside the tolerance window\");\n }\n\n const key = await globalThis.crypto.subtle.importKey(\n \"raw\",\n fromBase64(input.secret.replace(/^whsec_/, \"\")),\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n const expected = toBase64(\n await globalThis.crypto.subtle.sign(\n \"HMAC\",\n key,\n new TextEncoder().encode(`${id}.${timestamp}.${input.payload}`),\n ),\n );\n const valid = signatures\n .split(\" \")\n .some(\n (candidate) => candidate.startsWith(\"v1,\") && constantTimeEqual(candidate.slice(3), expected),\n );\n if (!valid) throw new WebhookVerificationError(\"Invalid webhook signature\");\n return JSON.parse(input.payload) as WebhookEvent;\n}\n"],"mappings":";AAEA,IAAa,2BAAb,cAA8C,MAAM;CAClD,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAIA,SAAS,OAAO,SAAuB,MAAkC;CACvE,IAAI,mBAAmB,SAAS,OAAO,QAAQ,IAAI,IAAI,KAAK,KAAA;CAC5D,MAAM,QAAQ,OAAO,QAAQ,OAAO,CAAC,CAAC,MAAM,CAAC,SAAS,IAAI,YAAY,MAAM,IAAI,CAAC,GAAG;CACpF,OAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK;AAC3C;AAEA,SAAS,WAAW,OAAwC;CAC1D,OAAO,WAAW,KAAK,KAAK,KAAK,IAAI,SAAS,KAAK,WAAW,CAAC,CAAC;AAClE;AAEA,SAAS,SAAS,OAA4B;CAC5C,OAAO,KAAK,OAAO,aAAa,GAAG,IAAI,WAAW,KAAK,CAAC,CAAC;AAC3D;AAEA,SAAS,kBAAkB,MAAc,OAAwB;CAC/D,MAAM,SAAS,KAAK,IAAI,KAAK,QAAQ,MAAM,MAAM;CAKjD,OAJiB,MAAM,KACrB,EAAE,OAAO,IACR,GAAG,UAAU,KAAK,WAAW,KAAK,IAAI,MAAM,WAAW,KAAK,CAC/D,CAAC,CAAC,QAAQ,aAAa,UAAU,cAAc,OAAO,KAAK,SAAS,MAAM,MAC5D,MAAM;AACtB;;;;;AAMA,eAAsB,cAAc,OAMV;CACxB,MAAM,KAAK,OAAO,MAAM,SAAS,YAAY;CAC7C,MAAM,YAAY,OAAO,MAAM,SAAS,mBAAmB;CAC3D,MAAM,aAAa,OAAO,MAAM,SAAS,mBAAmB;CAC5D,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,YACxB,MAAM,IAAI,yBAAyB,yBAAyB;CAG9D,MAAM,aAAa,KAAK,OAAO,MAAM,uBAAO,IAAI,KAAK,EAAA,CAAG,QAAQ,IAAI,GAAK;CACzE,MAAM,YAAY,MAAM,oBAAoB;CAC5C,IAAI,CAAC,QAAQ,KAAK,SAAS,KAAK,KAAK,IAAI,aAAa,OAAO,SAAS,CAAC,IAAI,WACzE,MAAM,IAAI,yBAAyB,mDAAmD;CAGxF,MAAM,MAAM,MAAM,WAAW,OAAO,OAAO,UACzC,OACA,WAAW,MAAM,OAAO,QAAQ,WAAW,EAAE,CAAC,GAC9C;EAAE,MAAM;EAAQ,MAAM;CAAU,GAChC,OACA,CAAC,MAAM,CACT;CACA,MAAM,WAAW,SACf,MAAM,WAAW,OAAO,OAAO,KAC7B,QACA,KACA,IAAI,YAAY,CAAC,CAAC,OAAO,GAAG,GAAG,GAAG,UAAU,GAAG,MAAM,SAAS,CAChE,CACF;CAMA,IAAI,CALU,WACX,MAAM,GAAG,CAAC,CACV,MACE,cAAc,UAAU,WAAW,KAAK,KAAK,kBAAkB,UAAU,MAAM,CAAC,GAAG,QAAQ,CAEvF,GAAG,MAAM,IAAI,yBAAyB,2BAA2B;CAC1E,OAAO,KAAK,MAAM,MAAM,OAAO;AACjC"}
|