@billium/node 1.0.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/dist/index.mjs ADDED
@@ -0,0 +1,441 @@
1
+ import { createHmac, timingSafeEqual } from 'crypto';
2
+
3
+ // src/errors.ts
4
+ var BilliumError = class extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "BilliumError";
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ }
10
+ };
11
+ var BilliumWebhookSignatureError = class extends BilliumError {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "BilliumWebhookSignatureError";
15
+ Object.setPrototypeOf(this, new.target.prototype);
16
+ }
17
+ };
18
+ var BilliumWebhookTimestampError = class extends BilliumError {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = "BilliumWebhookTimestampError";
22
+ Object.setPrototypeOf(this, new.target.prototype);
23
+ }
24
+ };
25
+ var BilliumApiError = class extends BilliumError {
26
+ constructor(message, status, code) {
27
+ super(message);
28
+ this.status = status;
29
+ this.code = code;
30
+ this.name = "BilliumApiError";
31
+ Object.setPrototypeOf(this, new.target.prototype);
32
+ }
33
+ };
34
+
35
+ // src/version.ts
36
+ var SDK_VERSION = "1.0.0";
37
+ var WebhooksClient = class {
38
+ constructor(defaultSecret, http, merchantId) {
39
+ this.defaultSecret = defaultSecret;
40
+ this.http = http;
41
+ this.basePath = merchantId ? `/api/v1/merchants/merchant/${merchantId}/webhooks` : void 0;
42
+ }
43
+ // ─── Signature verification ───────────────────────────────────────────────
44
+ /**
45
+ * Parses and verifies an incoming Billium webhook request.
46
+ *
47
+ * Signature header format: x-signature: t={unix_seconds},v1={hmac_sha256_hex}
48
+ * Signed string: "{unix_seconds}.{raw_body}"
49
+ * Algorithm: HMAC-SHA256
50
+ *
51
+ * If `webhookSecret` was passed to the `Billium` constructor, you can omit
52
+ * the `secret` argument here. Pass it explicitly to override.
53
+ *
54
+ * @param rawBody Raw request body as Buffer or string — must not be parsed
55
+ * @param signature Value of the `x-signature` header
56
+ * @param secret Webhook secret — can be omitted if set in the constructor
57
+ * @param options Optional config (tolerance window)
58
+ *
59
+ * @throws {BilliumError} No secret available
60
+ * @throws {BilliumWebhookSignatureError} Header malformed or HMAC mismatch
61
+ * @throws {BilliumWebhookTimestampError} Timestamp outside tolerance window
62
+ */
63
+ verify(rawBody, signature, secret, options = {}) {
64
+ const resolvedSecret = secret ?? this.defaultSecret;
65
+ if (!resolvedSecret) {
66
+ throw new BilliumError(
67
+ "A webhook secret is required. Pass it as the third argument or set webhookSecret in the Billium constructor."
68
+ );
69
+ }
70
+ return this.verifyInternal(rawBody, signature, resolvedSecret, options);
71
+ }
72
+ // ─── Webhook management ───────────────────────────────────────────────────
73
+ /**
74
+ * Creates a new webhook endpoint for the merchant.
75
+ *
76
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
77
+ * must be a **secret key** (`sk_*`) — public keys do not have webhook
78
+ * management scope.
79
+ */
80
+ async create(params) {
81
+ const http = this.managementHttp();
82
+ http.assertSecretKey("webhooks.create");
83
+ return http.post(this.managementPath(), params);
84
+ }
85
+ /**
86
+ * Lists all webhook endpoints for the merchant.
87
+ *
88
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
89
+ * must be a secret key (`sk_*`).
90
+ */
91
+ async list() {
92
+ const http = this.managementHttp();
93
+ http.assertSecretKey("webhooks.list");
94
+ return http.get(this.managementPath());
95
+ }
96
+ /**
97
+ * Updates a webhook endpoint.
98
+ *
99
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
100
+ * must be a secret key (`sk_*`).
101
+ */
102
+ async update(webhookId, params) {
103
+ const http = this.managementHttp();
104
+ http.assertSecretKey("webhooks.update");
105
+ return http.patch(
106
+ `${this.managementPath()}/${webhookId}`,
107
+ params
108
+ );
109
+ }
110
+ /**
111
+ * Deletes a webhook endpoint.
112
+ *
113
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
114
+ * must be a secret key (`sk_*`).
115
+ */
116
+ async delete(webhookId) {
117
+ const http = this.managementHttp();
118
+ http.assertSecretKey("webhooks.delete");
119
+ return http.delete(`${this.managementPath()}/${webhookId}`);
120
+ }
121
+ /**
122
+ * Sends a test ping to a webhook endpoint to verify it is reachable.
123
+ *
124
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
125
+ * must be a secret key (`sk_*`).
126
+ */
127
+ async ping(webhookId) {
128
+ const http = this.managementHttp();
129
+ http.assertSecretKey("webhooks.ping");
130
+ return http.post(`${this.managementPath()}/${webhookId}/ping`);
131
+ }
132
+ // ─── Internal helpers ─────────────────────────────────────────────────────
133
+ managementHttp() {
134
+ if (!this.http) {
135
+ throw new BilliumError(
136
+ "billium.webhooks management methods require both `apiKey` and `merchantId` to be set in the Billium constructor."
137
+ );
138
+ }
139
+ return this.http;
140
+ }
141
+ managementPath() {
142
+ if (!this.basePath) {
143
+ throw new BilliumError(
144
+ "billium.webhooks management methods require both `apiKey` and `merchantId` to be set in the Billium constructor."
145
+ );
146
+ }
147
+ return this.basePath;
148
+ }
149
+ verifyInternal(rawBody, signature, secret, options) {
150
+ const tolerance = options.tolerance ?? 300;
151
+ const { timestamp, v1 } = this.parseSignatureHeader(signature);
152
+ if (tolerance > 0) {
153
+ const nowSeconds = Math.floor(Date.now() / 1e3);
154
+ if (Math.abs(nowSeconds - timestamp) > tolerance) {
155
+ throw new BilliumWebhookTimestampError(
156
+ `Webhook timestamp is outside the tolerance window of ${tolerance} seconds.`
157
+ );
158
+ }
159
+ }
160
+ const body = typeof rawBody === "string" ? rawBody : rawBody.toString("utf8");
161
+ const expectedHex = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
162
+ if (!this.timingSafeCompare(expectedHex, v1)) {
163
+ throw new BilliumWebhookSignatureError(
164
+ "Webhook signature verification failed."
165
+ );
166
+ }
167
+ try {
168
+ return JSON.parse(body);
169
+ } catch {
170
+ throw new BilliumWebhookSignatureError(
171
+ "Webhook body is not valid JSON."
172
+ );
173
+ }
174
+ }
175
+ parseSignatureHeader(header) {
176
+ let rawTimestamp;
177
+ let v1;
178
+ for (const part of header.split(",")) {
179
+ if (part.startsWith("t=")) rawTimestamp = part.slice(2);
180
+ else if (part.startsWith("v1=")) v1 = part.slice(3);
181
+ }
182
+ if (!rawTimestamp || !v1) {
183
+ throw new BilliumWebhookSignatureError(
184
+ "Invalid x-signature header. Expected format: t={unix_seconds},v1={hmac_sha256_hex}"
185
+ );
186
+ }
187
+ const timestamp = parseInt(rawTimestamp, 10);
188
+ if (isNaN(timestamp) || timestamp <= 0) {
189
+ throw new BilliumWebhookSignatureError(
190
+ "Invalid timestamp in x-signature header."
191
+ );
192
+ }
193
+ return { timestamp, v1 };
194
+ }
195
+ timingSafeCompare(expected, actual) {
196
+ if (expected.length !== actual.length) return false;
197
+ const expectedBuf = Buffer.from(expected, "hex");
198
+ const actualBuf = Buffer.from(actual, "hex");
199
+ if (expectedBuf.length !== actualBuf.length) return false;
200
+ return timingSafeEqual(expectedBuf, actualBuf);
201
+ }
202
+ };
203
+
204
+ // src/invoices.ts
205
+ var InvoicesClient = class {
206
+ constructor(http, merchantId) {
207
+ this.http = http;
208
+ this.basePath = `/api/v1/merchants/merchant/${merchantId}/invoices`;
209
+ }
210
+ /**
211
+ * Creates a new invoice for the merchant.
212
+ *
213
+ * Pass `options.idempotencyKey` for safe retries — see
214
+ * {@link CreateInvoiceOptions.idempotencyKey} for the full rationale.
215
+ */
216
+ create(params, options = {}) {
217
+ const headers = options.idempotencyKey ? { "Idempotency-Key": options.idempotencyKey } : void 0;
218
+ return this.http.post(this.basePath, params, { headers });
219
+ }
220
+ /**
221
+ * Retrieves a single invoice by ID.
222
+ */
223
+ get(invoiceId) {
224
+ return this.http.get(`${this.basePath}/${invoiceId}`);
225
+ }
226
+ /**
227
+ * Lists invoices for the merchant with optional pagination and search.
228
+ */
229
+ list(params) {
230
+ return this.http.get(this.basePath, params);
231
+ }
232
+ /**
233
+ * Cancels an invoice. Only invoices in non-terminal states can be cancelled.
234
+ * Terminal states: PAID, UNDERPAID, OVERPAID, EXPIRED, CANCELLED.
235
+ *
236
+ * **Requires a secret key (`sk_*`).** Public keys (`pk_*`) cannot mutate
237
+ * existing invoices.
238
+ */
239
+ async cancel(invoiceId) {
240
+ this.http.assertSecretKey("invoices.cancel");
241
+ return this.http.post(`${this.basePath}/${invoiceId}/cancel`);
242
+ }
243
+ };
244
+
245
+ // src/http.ts
246
+ var DEFAULT_MAX_RETRIES = 2;
247
+ var DEFAULT_BASE_DELAY_MS = 500;
248
+ var DEFAULT_MAX_DELAY_MS = 3e4;
249
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
250
+ var ALWAYS_RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "PUT", "DELETE", "PATCH"]);
251
+ var USER_AGENT = `billium-node/${SDK_VERSION} (node/${process.version})`;
252
+ var PUBLIC_KEY_PREFIX = "pk_";
253
+ var HttpClient = class {
254
+ constructor(baseUrl, apiKey, options = {}) {
255
+ this.baseUrl = baseUrl.replace(/\/$/, "");
256
+ this.apiKey = apiKey;
257
+ this.isPublicKey = apiKey.startsWith(PUBLIC_KEY_PREFIX);
258
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
259
+ this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
260
+ this.maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
261
+ }
262
+ /**
263
+ * Throws a `BilliumError` if the configured key is a public key (`pk_*`).
264
+ *
265
+ * Used by methods that the backend won't accept a public key for —
266
+ * webhook management, invoice cancellation, anything that mutates state
267
+ * beyond invoice creation. Surfacing the error in the SDK rather than
268
+ * letting it round-trip to the backend gives developers a clear,
269
+ * actionable message instead of a generic `403 Insufficient permissions`.
270
+ *
271
+ * @param method - Dotted method name for the error message ("webhooks.create", etc.)
272
+ */
273
+ assertSecretKey(method) {
274
+ if (this.isPublicKey) {
275
+ throw new BilliumError(
276
+ `${method}() requires a secret key (sk_*). You passed a public key (pk_*), which only has scope for invoice.create, invoice.view, and product.view. Generate a secret key in the Billium dashboard under Settings \u2192 Developer \u2192 API keys.`
277
+ );
278
+ }
279
+ }
280
+ // ─── Verb wrappers ─────────────────────────────────────────────────────────
281
+ get(path, params) {
282
+ return this.request("GET", path, { params });
283
+ }
284
+ post(path, body, options = {}) {
285
+ return this.request("POST", path, { ...options, body });
286
+ }
287
+ put(path, body, options = {}) {
288
+ return this.request("PUT", path, { ...options, body });
289
+ }
290
+ patch(path, body, options = {}) {
291
+ return this.request("PATCH", path, { ...options, body });
292
+ }
293
+ delete(path, options = {}) {
294
+ return this.request("DELETE", path, options);
295
+ }
296
+ // ─── Internals ─────────────────────────────────────────────────────────────
297
+ async request(method, path, options = {}) {
298
+ const url = this.buildUrl(path, options.params);
299
+ const headers = {
300
+ ...this.headers(),
301
+ ...options.body !== void 0 ? { "Content-Type": "application/json" } : {},
302
+ ...options.headers ?? {}
303
+ };
304
+ const init = {
305
+ method,
306
+ headers,
307
+ body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
308
+ };
309
+ const canRetryMethod = ALWAYS_RETRYABLE_METHODS.has(method) || hasIdempotencyKey(headers);
310
+ const maxAttempts = canRetryMethod ? this.maxRetries + 1 : 1;
311
+ let lastNetworkError;
312
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
313
+ let res;
314
+ try {
315
+ res = await fetch(url, init);
316
+ } catch (err) {
317
+ lastNetworkError = err;
318
+ if (attempt < maxAttempts - 1) {
319
+ await this.sleep(this.computeDelay(attempt, null));
320
+ continue;
321
+ }
322
+ throw err;
323
+ }
324
+ if (!res.ok && RETRYABLE_STATUSES.has(res.status) && attempt < maxAttempts - 1) {
325
+ await this.sleep(this.computeDelay(attempt, res));
326
+ continue;
327
+ }
328
+ return this.parse(res);
329
+ }
330
+ throw lastNetworkError ?? new Error("HttpClient: retry loop exited unexpectedly");
331
+ }
332
+ headers() {
333
+ return {
334
+ "x-api-key": this.apiKey,
335
+ "User-Agent": USER_AGENT
336
+ };
337
+ }
338
+ buildUrl(path, params) {
339
+ const url = new URL(`${this.baseUrl}${path}`);
340
+ if (params) {
341
+ for (const [key, value] of Object.entries(params)) {
342
+ if (value === void 0 || value === null) continue;
343
+ url.searchParams.set(key, String(value));
344
+ }
345
+ }
346
+ return url.toString();
347
+ }
348
+ async parse(res) {
349
+ let body;
350
+ const contentType = res.headers.get("content-type") ?? "";
351
+ if (contentType.includes("application/json")) {
352
+ body = await res.json();
353
+ } else {
354
+ body = await res.text();
355
+ }
356
+ if (!res.ok) {
357
+ const message = body && typeof body === "object" && "message" in body && typeof body.message === "string" ? body.message : `Request failed with status ${res.status}`;
358
+ const code = body && typeof body === "object" && "error" in body && typeof body.error === "string" ? body.error : void 0;
359
+ throw new BilliumApiError(message, res.status, code);
360
+ }
361
+ return body;
362
+ }
363
+ /**
364
+ * Returns the number of milliseconds to wait before the next retry.
365
+ *
366
+ * Honors a `Retry-After` header on the response when present (parsed as
367
+ * either a delta-seconds integer or an HTTP date). Otherwise falls back
368
+ * to exponential backoff with full jitter — randomizing across the entire
369
+ * `[0, exponentialCeiling]` range to avoid retry storms when many
370
+ * clients fail simultaneously.
371
+ */
372
+ computeDelay(attempt, res) {
373
+ if (res) {
374
+ const retryAfter = res.headers.get("retry-after");
375
+ if (retryAfter) {
376
+ const seconds = parseInt(retryAfter, 10);
377
+ if (!isNaN(seconds) && seconds >= 0) {
378
+ return Math.min(seconds * 1e3, this.maxDelayMs);
379
+ }
380
+ const date = Date.parse(retryAfter);
381
+ if (!isNaN(date)) {
382
+ const diff = date - Date.now();
383
+ if (diff > 0) return Math.min(diff, this.maxDelayMs);
384
+ }
385
+ }
386
+ }
387
+ const exponentialCeiling = Math.min(
388
+ this.baseDelayMs * Math.pow(2, attempt),
389
+ this.maxDelayMs
390
+ );
391
+ return Math.floor(Math.random() * exponentialCeiling);
392
+ }
393
+ sleep(ms) {
394
+ return new Promise((resolve) => setTimeout(resolve, ms));
395
+ }
396
+ };
397
+ function hasIdempotencyKey(headers) {
398
+ return Object.keys(headers).some((k) => k.toLowerCase() === "idempotency-key");
399
+ }
400
+
401
+ // src/index.ts
402
+ var DEFAULT_BASE_URL = "https://api.billium.to";
403
+ var Billium = class {
404
+ constructor(options = {}) {
405
+ if (options.apiKey && options.merchantId) {
406
+ const http = new HttpClient(
407
+ options.baseUrl ?? DEFAULT_BASE_URL,
408
+ options.apiKey,
409
+ {
410
+ maxRetries: options.maxRetries,
411
+ baseDelayMs: options.baseDelayMs,
412
+ maxDelayMs: options.maxDelayMs
413
+ }
414
+ );
415
+ this.webhooks = new WebhooksClient(options.webhookSecret, http, options.merchantId);
416
+ this.invoices = new InvoicesClient(http, options.merchantId);
417
+ } else {
418
+ this.webhooks = new WebhooksClient(options.webhookSecret);
419
+ this.invoices = new InvoicesClient(
420
+ unconfiguredHttp(),
421
+ options.merchantId ?? ""
422
+ );
423
+ }
424
+ }
425
+ };
426
+ function unconfiguredHttp() {
427
+ const message = "billium.invoices and billium.webhooks management methods require both `apiKey` and `merchantId` to be set in the Billium constructor.";
428
+ return new Proxy({}, {
429
+ get() {
430
+ return () => Promise.reject(
431
+ Object.assign(new Error(message), {
432
+ name: "BilliumError"
433
+ })
434
+ );
435
+ }
436
+ });
437
+ }
438
+
439
+ export { Billium, BilliumApiError, BilliumError, BilliumWebhookSignatureError, BilliumWebhookTimestampError, SDK_VERSION };
440
+ //# sourceMappingURL=index.mjs.map
441
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/version.ts","../src/webhooks.ts","../src/invoices.ts","../src/http.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export class BilliumError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'BilliumError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BilliumWebhookSignatureError extends BilliumError {\n constructor(message: string) {\n super(message);\n this.name = 'BilliumWebhookSignatureError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BilliumWebhookTimestampError extends BilliumError {\n constructor(message: string) {\n super(message);\n this.name = 'BilliumWebhookTimestampError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BilliumApiError extends BilliumError {\n constructor(\n message: string,\n public readonly status: number,\n public readonly code?: string,\n ) {\n super(message);\n this.name = 'BilliumApiError';\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","/**\n * Single source of truth for the SDK version reported in the User-Agent\n * header. **Keep this in sync with `package.json#version`** when bumping.\n *\n * Hardcoded rather than imported from package.json so that the build does\n * not bundle the entire package.json blob (with dev/test config) into the\n * production output.\n */\nexport const SDK_VERSION = '1.0.0';\n","import { createHmac, timingSafeEqual } from 'crypto';\n\nimport {\n BilliumError,\n BilliumWebhookSignatureError,\n BilliumWebhookTimestampError,\n} from './errors';\nimport { HttpClient } from './http';\n\n// ─── Webhook event types ──────────────────────────────────────────────────────\n\nexport type WebhookEventType =\n // Invoice\n | 'invoice.*'\n | 'invoice.created'\n | 'invoice.updated'\n | 'invoice.paid'\n | 'invoice.underpaid'\n | 'invoice.overpaid'\n | 'invoice.expired'\n | 'invoice.cancelled'\n // Payment\n | 'payment.*'\n | 'payment.created'\n | 'payment.updated'\n | 'payment.detected'\n | 'payment.confirmed'\n | 'payment.paid'\n | 'payment.underpaid'\n | 'payment.overpaid'\n | 'payment.expired';\n\n// ─── Webhook management types ─────────────────────────────────────────────────\n\nexport interface WebhookSecret {\n id: string;\n webhookId: string;\n secretKeyPreview: string;\n isActive: boolean;\n expiresAt?: string;\n}\n\nexport interface Webhook {\n id: string;\n merchantId: string;\n url: string;\n events: WebhookEventType[];\n isActive: boolean;\n description?: string;\n /** Number of retry attempts on failure (0–10). */\n retryCount: number;\n /** Request timeout in milliseconds (1000–30000). */\n timeout: number;\n webhookSecrets: WebhookSecret[];\n}\n\nexport interface CreateWebhookParams {\n /** The HTTPS URL Billium will POST events to. */\n url: string;\n /** List of event types to subscribe to. Use `'invoice.*'` or `'payment.*'` to subscribe to all events in a category. */\n events: WebhookEventType[];\n /** Optional description for this endpoint. */\n description?: string;\n /** Whether the webhook is active on creation. Defaults to `true`. */\n isActive?: boolean;\n /** Number of retry attempts on delivery failure (0–10). Defaults to `3`. */\n retryCount?: number;\n /** Request timeout in milliseconds (1000–30000). Defaults to `30000`. */\n timeout?: number;\n}\n\nexport type UpdateWebhookParams = Partial<CreateWebhookParams>;\n\n// ─── Signature verification types ────────────────────────────────────────────\n\nexport interface WebhookEvent {\n event: string;\n id: string;\n data: unknown;\n timestamp: string;\n}\n\nexport interface VerifyOptions {\n /**\n * Maximum allowed age of the webhook timestamp in seconds.\n * Set to 0 to disable the check.\n * @default 300\n */\n tolerance?: number;\n}\n\n// ─── Client ──────────────────────────────────────────────────────────────────\n\nexport class WebhooksClient {\n private readonly basePath: string | undefined;\n\n constructor(\n private readonly defaultSecret?: string,\n private readonly http?: HttpClient,\n merchantId?: string,\n ) {\n this.basePath = merchantId\n ? `/api/v1/merchants/merchant/${merchantId}/webhooks`\n : undefined;\n }\n\n // ─── Signature verification ───────────────────────────────────────────────\n\n /**\n * Parses and verifies an incoming Billium webhook request.\n *\n * Signature header format: x-signature: t={unix_seconds},v1={hmac_sha256_hex}\n * Signed string: \"{unix_seconds}.{raw_body}\"\n * Algorithm: HMAC-SHA256\n *\n * If `webhookSecret` was passed to the `Billium` constructor, you can omit\n * the `secret` argument here. Pass it explicitly to override.\n *\n * @param rawBody Raw request body as Buffer or string — must not be parsed\n * @param signature Value of the `x-signature` header\n * @param secret Webhook secret — can be omitted if set in the constructor\n * @param options Optional config (tolerance window)\n *\n * @throws {BilliumError} No secret available\n * @throws {BilliumWebhookSignatureError} Header malformed or HMAC mismatch\n * @throws {BilliumWebhookTimestampError} Timestamp outside tolerance window\n */\n verify(\n rawBody: Buffer | string,\n signature: string,\n secret?: string,\n options: VerifyOptions = {},\n ): WebhookEvent {\n const resolvedSecret = secret ?? this.defaultSecret;\n if (!resolvedSecret) {\n throw new BilliumError(\n 'A webhook secret is required. Pass it as the third argument or set webhookSecret in the Billium constructor.',\n );\n }\n return this.verifyInternal(rawBody, signature, resolvedSecret, options);\n }\n\n // ─── Webhook management ───────────────────────────────────────────────────\n\n /**\n * Creates a new webhook endpoint for the merchant.\n *\n * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`\n * must be a **secret key** (`sk_*`) — public keys do not have webhook\n * management scope.\n */\n async create(params: CreateWebhookParams): Promise<Webhook> {\n const http = this.managementHttp();\n http.assertSecretKey('webhooks.create');\n return http.post<Webhook>(this.managementPath(), params);\n }\n\n /**\n * Lists all webhook endpoints for the merchant.\n *\n * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`\n * must be a secret key (`sk_*`).\n */\n async list(): Promise<Webhook[]> {\n const http = this.managementHttp();\n http.assertSecretKey('webhooks.list');\n return http.get<Webhook[]>(this.managementPath());\n }\n\n /**\n * Updates a webhook endpoint.\n *\n * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`\n * must be a secret key (`sk_*`).\n */\n async update(webhookId: string, params: UpdateWebhookParams): Promise<Webhook> {\n const http = this.managementHttp();\n http.assertSecretKey('webhooks.update');\n return http.patch<Webhook>(\n `${this.managementPath()}/${webhookId}`,\n params,\n );\n }\n\n /**\n * Deletes a webhook endpoint.\n *\n * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`\n * must be a secret key (`sk_*`).\n */\n async delete(webhookId: string): Promise<void> {\n const http = this.managementHttp();\n http.assertSecretKey('webhooks.delete');\n return http.delete<void>(`${this.managementPath()}/${webhookId}`);\n }\n\n /**\n * Sends a test ping to a webhook endpoint to verify it is reachable.\n *\n * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`\n * must be a secret key (`sk_*`).\n */\n async ping(webhookId: string): Promise<void> {\n const http = this.managementHttp();\n http.assertSecretKey('webhooks.ping');\n return http.post<void>(`${this.managementPath()}/${webhookId}/ping`);\n }\n\n // ─── Internal helpers ─────────────────────────────────────────────────────\n\n private managementHttp(): HttpClient {\n if (!this.http) {\n throw new BilliumError(\n 'billium.webhooks management methods require both `apiKey` and `merchantId` to be set in the Billium constructor.',\n );\n }\n return this.http;\n }\n\n private managementPath(): string {\n if (!this.basePath) {\n throw new BilliumError(\n 'billium.webhooks management methods require both `apiKey` and `merchantId` to be set in the Billium constructor.',\n );\n }\n return this.basePath;\n }\n\n private verifyInternal(\n rawBody: Buffer | string,\n signature: string,\n secret: string,\n options: VerifyOptions,\n ): WebhookEvent {\n const tolerance = options.tolerance ?? 300;\n\n const { timestamp, v1 } = this.parseSignatureHeader(signature);\n\n if (tolerance > 0) {\n const nowSeconds = Math.floor(Date.now() / 1000);\n if (Math.abs(nowSeconds - timestamp) > tolerance) {\n throw new BilliumWebhookTimestampError(\n `Webhook timestamp is outside the tolerance window of ${tolerance} seconds.`,\n );\n }\n }\n\n const body =\n typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8');\n\n const expectedHex = createHmac('sha256', secret)\n .update(`${timestamp}.${body}`)\n .digest('hex');\n\n if (!this.timingSafeCompare(expectedHex, v1)) {\n throw new BilliumWebhookSignatureError(\n 'Webhook signature verification failed.',\n );\n }\n\n try {\n return JSON.parse(body) as WebhookEvent;\n } catch {\n throw new BilliumWebhookSignatureError(\n 'Webhook body is not valid JSON.',\n );\n }\n }\n\n private parseSignatureHeader(header: string): {\n timestamp: number;\n v1: string;\n } {\n let rawTimestamp: string | undefined;\n let v1: string | undefined;\n\n for (const part of header.split(',')) {\n if (part.startsWith('t=')) rawTimestamp = part.slice(2);\n else if (part.startsWith('v1=')) v1 = part.slice(3);\n }\n\n if (!rawTimestamp || !v1) {\n throw new BilliumWebhookSignatureError(\n 'Invalid x-signature header. Expected format: t={unix_seconds},v1={hmac_sha256_hex}',\n );\n }\n\n const timestamp = parseInt(rawTimestamp, 10);\n if (isNaN(timestamp) || timestamp <= 0) {\n throw new BilliumWebhookSignatureError(\n 'Invalid timestamp in x-signature header.',\n );\n }\n\n return { timestamp, v1 };\n }\n\n private timingSafeCompare(expected: string, actual: string): boolean {\n if (expected.length !== actual.length) return false;\n const expectedBuf = Buffer.from(expected, 'hex');\n const actualBuf = Buffer.from(actual, 'hex');\n if (expectedBuf.length !== actualBuf.length) return false;\n return timingSafeEqual(expectedBuf, actualBuf);\n }\n}\n","import { HttpClient } from './http';\n\n// ─── Invoice types ──────────────────────────────────────────────────────────\n\nexport type InvoiceStatus =\n | 'AWAITING_PAYMENT'\n | 'PENDING_CONFIRMATION'\n | 'PAID'\n | 'UNDERPAID'\n | 'OVERPAID'\n | 'EXPIRED'\n | 'CANCELLED';\n\n/**\n * Customer attached to an invoice. Returned as a nested relation, not as\n * flat `customerEmail` / `customerName` fields, because that mirrors the\n * underlying data model: a customer is a separate entity that can be linked\n * to multiple invoices.\n */\nexport interface InvoiceCustomer {\n id: string;\n merchantId: string;\n email: string;\n name: string | null;\n address: string | null;\n phoneNumber: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * Product an invoice was generated from, when applicable. Only set for\n * invoices created via the public product checkout endpoint.\n */\nexport interface InvoiceProduct {\n id: string;\n name: string;\n description: string | null;\n price: string;\n currency: string;\n image: string | null;\n}\n\n/**\n * A single transition in an invoice's status history. Useful for rendering\n * a timeline in a merchant dashboard.\n */\nexport interface InvoiceTimelineEntry {\n id: string;\n invoiceId: string;\n paymentStatus: InvoiceStatus;\n time: string;\n}\n\n/**\n * On-chain payment received against an invoice. An invoice can have zero\n * payments (still awaiting), one (typical), or many (split payments,\n * overpayments, etc.).\n */\nexport interface InvoicePayment {\n id: string;\n invoiceId: string;\n chainId: number | null;\n /** Symbol of the chain's native coin (e.g. 'BTC', 'ETH'). */\n network: string | null;\n txHash: string | null;\n fromAddress: string | null;\n toAddress: string | null;\n amount: string;\n currency: string;\n status: string;\n receivedTxAt: string | null;\n confirmedTxAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\n/**\n * An invoice as returned by the Billium merchant API.\n *\n * **Note on numeric fields:** `rawAmount` and `endAmount` are strings, not\n * numbers, because they're stored as `Decimal(15, 6)` in the database and\n * serialized as strings to preserve precision. Use a decimal library\n * (e.g. `decimal.js`) for arithmetic.\n *\n * **Note on relations:** `customer`, `product`, `payments`, and\n * `invoiceTimeline` are always present on responses from `create()`,\n * `get()`, `list()`, and `cancel()`. They will be `null` / `[]` when\n * empty, never `undefined`.\n */\nexport interface Invoice {\n id: string;\n merchantId: string;\n customerId: string | null;\n productId: string | null;\n name: string;\n description: string | null;\n redirectUrl: string | null;\n /** Decimal serialized as string. The amount the merchant set on the invoice. */\n rawAmount: string;\n /** Decimal serialized as string. The amount the customer actually pays (rawAmount + fees, when applicable). */\n endAmount: string;\n currency: string;\n status: InvoiceStatus;\n expiresAt: string | null;\n createdAt: string;\n updatedAt: string;\n\n // Relations — always included on merchant API responses.\n customer: InvoiceCustomer | null;\n product: InvoiceProduct | null;\n payments: InvoicePayment[];\n invoiceTimeline: InvoiceTimelineEntry[];\n}\n\nexport interface PaginatedResult<T> {\n data: T[];\n total: number;\n page: number;\n limit: number;\n}\n\n// ─── Input types ─────────────────────────────────────────────────────────────\n\nexport interface CreateInvoiceParams {\n /** Invoice display name. */\n name: string;\n /** Amount in the specified currency. */\n rawAmount: number;\n /** Currency code (e.g. 'USD'). Defaults to 'USD'. */\n currency?: string;\n /** Customer email address. */\n customerEmail?: string;\n /** Customer full name. */\n customerName?: string;\n /** Customer billing address. */\n customerAddress?: string;\n /** Customer phone number. */\n customerPhoneNumber?: string;\n /** Optional description shown on the invoice. */\n description?: string;\n /** URL to redirect the customer after payment. */\n redirectUrl?: string;\n}\n\nexport interface ListInvoicesParams {\n /** Page number (1-based). Defaults to 1. */\n page?: number;\n /** Number of results per page (max 100). Defaults to 10. */\n limit?: number;\n /** Search by invoice name, description, or ID. */\n search?: string;\n}\n\n// ─── Request options ─────────────────────────────────────────────────────────\n\n/**\n * Per-call options accepted by `invoices.create()`.\n */\nexport interface CreateInvoiceOptions {\n /**\n * A unique key (≤ 255 chars, scoped to your merchant) that the server uses\n * to deduplicate retried requests. If you call `create()` twice with the\n * same key and same body within 24 hours, the second call returns the\n * exact response from the first — no duplicate invoice is created.\n *\n * **You should set this on every `create()` call** in production. The SDK\n * also requires it for transparent retries on POST: without this header,\n * the SDK will not retry a failed `create()` call (even on `503`),\n * because it cannot prove the server didn't already accept the first\n * attempt.\n *\n * Generate a fresh key per logical operation (e.g. one per checkout\n * session, one per cart submission). A UUID v4 is a good default:\n *\n * ```typescript\n * import { randomUUID } from 'crypto';\n *\n * const invoice = await billium.invoices.create(\n * { name: 'Order #1234', rawAmount: 99.99 },\n * { idempotencyKey: randomUUID() },\n * );\n * ```\n *\n * If the server sees the same key with a *different* body, it returns\n * `409 Conflict` — that's a programming bug, not a transient error.\n */\n idempotencyKey?: string;\n}\n\n// ─── Client ──────────────────────────────────────────────────────────────────\n\nexport class InvoicesClient {\n private readonly basePath: string;\n\n constructor(\n private readonly http: HttpClient,\n merchantId: string,\n ) {\n this.basePath = `/api/v1/merchants/merchant/${merchantId}/invoices`;\n }\n\n /**\n * Creates a new invoice for the merchant.\n *\n * Pass `options.idempotencyKey` for safe retries — see\n * {@link CreateInvoiceOptions.idempotencyKey} for the full rationale.\n */\n create(\n params: CreateInvoiceParams,\n options: CreateInvoiceOptions = {},\n ): Promise<Invoice> {\n const headers: Record<string, string> | undefined = options.idempotencyKey\n ? { 'Idempotency-Key': options.idempotencyKey }\n : undefined;\n return this.http.post<Invoice>(this.basePath, params, { headers });\n }\n\n /**\n * Retrieves a single invoice by ID.\n */\n get(invoiceId: string): Promise<Invoice> {\n return this.http.get<Invoice>(`${this.basePath}/${invoiceId}`);\n }\n\n /**\n * Lists invoices for the merchant with optional pagination and search.\n */\n list(params?: ListInvoicesParams): Promise<PaginatedResult<Invoice>> {\n return this.http.get<PaginatedResult<Invoice>>(this.basePath, params);\n }\n\n /**\n * Cancels an invoice. Only invoices in non-terminal states can be cancelled.\n * Terminal states: PAID, UNDERPAID, OVERPAID, EXPIRED, CANCELLED.\n *\n * **Requires a secret key (`sk_*`).** Public keys (`pk_*`) cannot mutate\n * existing invoices.\n */\n async cancel(invoiceId: string): Promise<Invoice> {\n // Marked async so that the synchronous throw inside `assertSecretKey`\n // becomes a rejected promise — `cancel(...).catch(...)` and\n // `await cancel(...)` should both see the same error path.\n this.http.assertSecretKey('invoices.cancel');\n return this.http.post<Invoice>(`${this.basePath}/${invoiceId}/cancel`);\n }\n}\n","import { BilliumApiError, BilliumError } from './errors';\nimport { SDK_VERSION } from './version';\n\n// ─── Defaults ────────────────────────────────────────────────────────────────\n\nconst DEFAULT_MAX_RETRIES = 2;\nconst DEFAULT_BASE_DELAY_MS = 500;\nconst DEFAULT_MAX_DELAY_MS = 30_000;\n\n// HTTP statuses where a retry is worth trying. 5xx are usually transient\n// (database blip, deploy in progress, upstream timeout), and 429 means the\n// server is asking us to back off — Retry-After tells us how long.\nconst RETRYABLE_STATUSES = new Set<number>([429, 500, 502, 503, 504]);\n\n// HTTP methods we will retry without an explicit opt-in. POST is intentionally\n// excluded because it can create resources — retrying a POST that already\n// succeeded server-side (but the response was lost in transit) would create a\n// duplicate. POSTs only get retried when the caller passes an Idempotency-Key\n// header, which lets the server deduplicate the second attempt.\nconst ALWAYS_RETRYABLE_METHODS = new Set(['GET', 'PUT', 'DELETE', 'PATCH']);\n\n// User-Agent reported by every request. Sent so backend operators can\n// segment traffic by SDK version (e.g. for deprecation metrics or per-SDK\n// rate limit policies). The Node version helps debug issues that only\n// reproduce on certain runtimes.\nconst USER_AGENT = `billium-node/${SDK_VERSION} (node/${process.version})`;\n\n// Prefix Billium uses to mark public (browser-safe, scope-limited) API keys.\n// PUBLIC keys can only call invoice.create / invoice.view / product.view —\n// they specifically cannot call any of the webhook management endpoints,\n// invoices.cancel, or anything that mutates state beyond invoice creation.\n//\n// The SDK detects this prefix at construct time and surfaces a clear error\n// when a user calls a method that requires a SECRET key, instead of letting\n// the request hit the backend and come back with a generic 403.\nconst PUBLIC_KEY_PREFIX = 'pk_';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface HttpClientOptions {\n /**\n * Maximum number of retry attempts after the initial request fails.\n * Total HTTP calls = maxRetries + 1. Set to 0 to disable retries entirely.\n * @default 2\n */\n maxRetries?: number;\n\n /**\n * Initial backoff delay in milliseconds. Subsequent retries use\n * exponential backoff with full jitter, capped at `maxDelayMs`.\n * @default 500\n */\n baseDelayMs?: number;\n\n /**\n * Upper bound for the backoff delay between retries.\n * @default 30000\n */\n maxDelayMs?: number;\n}\n\n/**\n * Acceptable shape for query string params: any object whose values are\n * primitives the URL spec can serialize. `undefined` and `null` values are\n * dropped at serialization time (so callers can spread optional fields\n * without filtering them first), and non-primitive values are skipped.\n *\n * Typed as `Readonly<Record<string, unknown>>` rather than a stricter union\n * so user-defined interfaces with optional properties (e.g.\n * `{ page?: number; limit?: number }`) can be passed directly without an\n * `as Record<…>` cast — TypeScript's \"weak type detection\" otherwise\n * refuses to widen interfaces with all-optional properties into a stricter\n * value union.\n */\nexport type QueryParams = Readonly<Record<string, unknown>>;\n\nexport interface RequestOptions {\n body?: unknown;\n params?: QueryParams;\n headers?: Record<string, string>;\n}\n\n// ─── Client ──────────────────────────────────────────────────────────────────\n\nexport class HttpClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly isPublicKey: boolean;\n private readonly maxRetries: number;\n private readonly baseDelayMs: number;\n private readonly maxDelayMs: number;\n\n constructor(baseUrl: string, apiKey: string, options: HttpClientOptions = {}) {\n this.baseUrl = baseUrl.replace(/\\/$/, '');\n this.apiKey = apiKey;\n // Detect public-key prefix once, at construct time. We don't validate\n // any other format here — letting the backend reject unknown prefixes\n // with 401 keeps the SDK forward-compatible with future key formats.\n this.isPublicKey = apiKey.startsWith(PUBLIC_KEY_PREFIX);\n this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.baseDelayMs = options.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;\n this.maxDelayMs = options.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;\n }\n\n /**\n * Throws a `BilliumError` if the configured key is a public key (`pk_*`).\n *\n * Used by methods that the backend won't accept a public key for —\n * webhook management, invoice cancellation, anything that mutates state\n * beyond invoice creation. Surfacing the error in the SDK rather than\n * letting it round-trip to the backend gives developers a clear,\n * actionable message instead of a generic `403 Insufficient permissions`.\n *\n * @param method - Dotted method name for the error message (\"webhooks.create\", etc.)\n */\n assertSecretKey(method: string): void {\n if (this.isPublicKey) {\n throw new BilliumError(\n `${method}() requires a secret key (sk_*). You passed a public key (pk_*), ` +\n `which only has scope for invoice.create, invoice.view, and product.view. ` +\n `Generate a secret key in the Billium dashboard under Settings → Developer → API keys.`,\n );\n }\n }\n\n // ─── Verb wrappers ─────────────────────────────────────────────────────────\n\n get<T>(path: string, params?: object): Promise<T> {\n // Accepts `object` at the boundary (not `QueryParams`) so user-defined\n // interfaces with all-optional properties — `{ page?: number }` and\n // friends — can be passed without an `as Record<…>` cast on the\n // caller side. The internal `request()` path coerces values to strings\n // when building the URL, so any plain object whose values are\n // string-coercible will work.\n return this.request<T>('GET', path, { params: params as QueryParams });\n }\n\n post<T>(path: string, body?: unknown, options: RequestOptions = {}): Promise<T> {\n return this.request<T>('POST', path, { ...options, body });\n }\n\n put<T>(path: string, body?: unknown, options: RequestOptions = {}): Promise<T> {\n return this.request<T>('PUT', path, { ...options, body });\n }\n\n patch<T>(path: string, body?: unknown, options: RequestOptions = {}): Promise<T> {\n return this.request<T>('PATCH', path, { ...options, body });\n }\n\n delete<T>(path: string, options: RequestOptions = {}): Promise<T> {\n return this.request<T>('DELETE', path, options);\n }\n\n // ─── Internals ─────────────────────────────────────────────────────────────\n\n private async request<T>(\n method: string,\n path: string,\n options: RequestOptions = {},\n ): Promise<T> {\n const url = this.buildUrl(path, options.params);\n\n // Per-request headers override the defaults so callers can drop in things\n // like `Idempotency-Key` without losing auth or content-type.\n const headers: Record<string, string> = {\n ...this.headers(),\n ...(options.body !== undefined ? { 'Content-Type': 'application/json' } : {}),\n ...(options.headers ?? {}),\n };\n\n const init: RequestInit = {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n };\n\n const canRetryMethod =\n ALWAYS_RETRYABLE_METHODS.has(method) || hasIdempotencyKey(headers);\n\n const maxAttempts = canRetryMethod ? this.maxRetries + 1 : 1;\n\n let lastNetworkError: unknown;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n let res: Response;\n try {\n res = await fetch(url, init);\n } catch (err) {\n // Network failure (DNS, ECONNREFUSED, TLS handshake, AbortError…).\n // fetch throws TypeError or DOMException — never a BilliumError.\n lastNetworkError = err;\n if (attempt < maxAttempts - 1) {\n await this.sleep(this.computeDelay(attempt, null));\n continue;\n }\n throw err;\n }\n\n // Successful HTTP exchange but the server is asking us to back off.\n // Don't read the body yet — we'll just retry. Reading would consume it\n // and force us to clone for the next attempt.\n if (\n !res.ok &&\n RETRYABLE_STATUSES.has(res.status) &&\n attempt < maxAttempts - 1\n ) {\n await this.sleep(this.computeDelay(attempt, res));\n continue;\n }\n\n // Either the response was OK, or it failed with a non-retryable code\n // (4xx mostly), or we've exhausted retries on a retryable status.\n // Either way, parse it now and let parse() throw on non-2xx.\n return this.parse<T>(res);\n }\n\n // Unreachable in practice — the loop either returns or throws on the\n // last iteration. Kept to satisfy strict TypeScript control-flow analysis.\n throw lastNetworkError ?? new Error('HttpClient: retry loop exited unexpectedly');\n }\n\n private headers(): Record<string, string> {\n return {\n 'x-api-key': this.apiKey,\n 'User-Agent': USER_AGENT,\n };\n }\n\n private buildUrl(path: string, params?: QueryParams): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n // Drop undefined/null so callers can spread optional fields\n // straight into the params bag without filtering them first.\n if (value === undefined || value === null) continue;\n url.searchParams.set(key, String(value));\n }\n }\n return url.toString();\n }\n\n private async parse<T>(res: Response): Promise<T> {\n let body: unknown;\n const contentType = res.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) {\n body = await res.json();\n } else {\n body = await res.text();\n }\n\n if (!res.ok) {\n const message =\n body &&\n typeof body === 'object' &&\n 'message' in body &&\n typeof (body as Record<string, unknown>).message === 'string'\n ? ((body as Record<string, unknown>).message as string)\n : `Request failed with status ${res.status}`;\n\n const code =\n body &&\n typeof body === 'object' &&\n 'error' in body &&\n typeof (body as Record<string, unknown>).error === 'string'\n ? ((body as Record<string, unknown>).error as string)\n : undefined;\n\n throw new BilliumApiError(message, res.status, code);\n }\n\n return body as T;\n }\n\n /**\n * Returns the number of milliseconds to wait before the next retry.\n *\n * Honors a `Retry-After` header on the response when present (parsed as\n * either a delta-seconds integer or an HTTP date). Otherwise falls back\n * to exponential backoff with full jitter — randomizing across the entire\n * `[0, exponentialCeiling]` range to avoid retry storms when many\n * clients fail simultaneously.\n */\n private computeDelay(attempt: number, res: Response | null): number {\n if (res) {\n const retryAfter = res.headers.get('retry-after');\n if (retryAfter) {\n // Try integer seconds first (most common form).\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds) && seconds >= 0) {\n return Math.min(seconds * 1000, this.maxDelayMs);\n }\n // Then HTTP date form (\"Wed, 21 Oct 2026 07:28:00 GMT\").\n const date = Date.parse(retryAfter);\n if (!isNaN(date)) {\n const diff = date - Date.now();\n if (diff > 0) return Math.min(diff, this.maxDelayMs);\n }\n }\n }\n\n const exponentialCeiling = Math.min(\n this.baseDelayMs * Math.pow(2, attempt),\n this.maxDelayMs,\n );\n return Math.floor(Math.random() * exponentialCeiling);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * Returns true if `headers` contains an `Idempotency-Key` entry (case\n * insensitive). When set, the server is expected to deduplicate retries on\n * POST endpoints, so the client is free to retry safely.\n */\nfunction hasIdempotencyKey(headers: Record<string, string>): boolean {\n return Object.keys(headers).some((k) => k.toLowerCase() === 'idempotency-key');\n}\n","export {\n BilliumError,\n BilliumApiError,\n BilliumWebhookSignatureError,\n BilliumWebhookTimestampError,\n} from './errors';\n\nexport { SDK_VERSION } from './version';\n\nexport type {\n WebhookEvent,\n VerifyOptions,\n WebhookEventType,\n Webhook,\n WebhookSecret,\n CreateWebhookParams,\n UpdateWebhookParams,\n} from './webhooks';\nexport type {\n Invoice,\n InvoiceStatus,\n InvoiceCustomer,\n InvoiceProduct,\n InvoicePayment,\n InvoiceTimelineEntry,\n CreateInvoiceParams,\n CreateInvoiceOptions,\n ListInvoicesParams,\n PaginatedResult,\n} from './invoices';\n\nimport { WebhooksClient } from './webhooks';\nimport { InvoicesClient } from './invoices';\nimport { HttpClient } from './http';\n\nconst DEFAULT_BASE_URL = 'https://api.billium.to';\n\nexport interface BilliumOptions {\n /**\n * Your Billium **secret** API key (`sk_*`). Required to use\n * `billium.invoices` and `billium.webhooks` management methods.\n *\n * Generate a key in the Billium dashboard under Settings → Developer →\n * API keys. The dashboard issues two key types — public (`pk_*`) and\n * secret (`sk_*`) — but the Node SDK only consumes secret keys, since\n * every method it exposes mutates state or accesses webhook management\n * endpoints that public keys aren't authorized for. Public keys are\n * reserved for browser-side SDKs (vanilla JS, React, etc.) which will\n * ship as separate packages.\n *\n * Passing a `pk_*` key here is allowed, but methods like\n * `webhooks.create()` and `invoices.cancel()` will throw a\n * `BilliumError` immediately rather than round-tripping a 403.\n */\n apiKey?: string;\n\n /**\n * Your merchant ID (mer_...).\n * Required to use `billium.invoices`.\n */\n merchantId?: string;\n\n /**\n * Your webhook secret (whsec_...).\n * When set, you can call `billium.webhooks.verify(body, signature)` without\n * passing the secret as a third argument on every call.\n */\n webhookSecret?: string;\n\n /**\n * Override the API base URL. Useful for self-hosted deployments or testing.\n * @default 'https://api.billium.to'\n */\n baseUrl?: string;\n\n /**\n * Maximum number of retry attempts for failed requests.\n * Total HTTP calls = `maxRetries + 1`. Set to 0 to disable retries.\n *\n * Retries fire on network errors, 5xx responses, and 429 (rate limited).\n * `Retry-After` headers from the server are honored when present.\n *\n * `GET`, `PUT`, `PATCH`, and `DELETE` are always retried when\n * `maxRetries > 0`. `POST` is retried only when an `Idempotency-Key`\n * header is set on the request — otherwise retrying could create\n * duplicate resources.\n *\n * @default 2\n */\n maxRetries?: number;\n\n /**\n * Initial backoff delay in milliseconds. Retries use exponential backoff\n * with full jitter, capped at `maxDelayMs`.\n * @default 500\n */\n baseDelayMs?: number;\n\n /**\n * Upper bound for the backoff delay between retries.\n * @default 30000\n */\n maxDelayMs?: number;\n}\n\nexport class Billium {\n readonly webhooks: WebhooksClient;\n readonly invoices: InvoicesClient;\n\n constructor(options: BilliumOptions = {}) {\n if (options.apiKey && options.merchantId) {\n const http = new HttpClient(\n options.baseUrl ?? DEFAULT_BASE_URL,\n options.apiKey,\n {\n maxRetries: options.maxRetries,\n baseDelayMs: options.baseDelayMs,\n maxDelayMs: options.maxDelayMs,\n },\n );\n this.webhooks = new WebhooksClient(options.webhookSecret, http, options.merchantId);\n this.invoices = new InvoicesClient(http, options.merchantId);\n } else {\n this.webhooks = new WebhooksClient(options.webhookSecret);\n this.invoices = new InvoicesClient(\n unconfiguredHttp(),\n options.merchantId ?? '',\n );\n }\n }\n}\n\n// `Billium` is exported as a named export only (no `export default`), which:\n// - Eliminates the tsup CJS warning about mixed named+default exports.\n// - Avoids the `require('@billium/node').default` foot-gun in CJS.\n// - Tree-shakes cleanly in bundlers.\n// - Matches the 2024+ ecosystem norm (Resend, Clerk, Supabase, etc.).\n//\n// Usage:\n// ESM: import { Billium } from '@billium/node';\n// CJS: const { Billium } = require('@billium/node');\n\n// ─── Internal ────────────────────────────────────────────────────────────────\n\n/**\n * Placeholder `HttpClient` used when only `webhookSecret` is configured\n * (i.e. the consumer only needs to verify webhook signatures, not call\n * the REST API).\n *\n * Returns a Proxy that throws a clear, actionable error on **any** property\n * access — get, post, patch, delete, future verbs we haven't added yet —\n * so callers don't get misleading \"x is not a function\" runtime errors.\n *\n * Using a Proxy instead of a hand-rolled object means we don't have to\n * remember to update this every time `HttpClient` grows a new method, and\n * the unsafe `as unknown as HttpClient` cast is gone.\n */\nfunction unconfiguredHttp(): HttpClient {\n const message =\n 'billium.invoices and billium.webhooks management methods require ' +\n 'both `apiKey` and `merchantId` to be set in the Billium constructor.';\n\n return new Proxy({} as HttpClient, {\n get() {\n // Return a function that rejects, so both `await client.invoices.list()`\n // and `client.invoices.list().then(...)` surface the same error.\n return () =>\n Promise.reject(\n Object.assign(new Error(message), {\n name: 'BilliumError',\n }),\n );\n },\n });\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@billium/node",
3
+ "version": "1.0.0",
4
+ "description": "Official Billium Node.js SDK — non-custodial crypto payments, invoices, and webhooks.",
5
+ "keywords": [
6
+ "billium",
7
+ "webhook",
8
+ "crypto",
9
+ "payments",
10
+ "invoice",
11
+ "bitcoin",
12
+ "ethereum",
13
+ "non-custodial",
14
+ "stablecoins",
15
+ "sdk"
16
+ ],
17
+ "license": "MIT",
18
+ "author": "Billium <hello@billium.to>",
19
+ "homepage": "https://billium.to",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/BilliumHQ/billium-node.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/BilliumHQ/billium-node/issues"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.mjs",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.mjs",
34
+ "require": "./dist/index.js"
35
+ }
36
+ },
37
+ "sideEffects": false,
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE",
42
+ "CHANGELOG.md"
43
+ ],
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": true
47
+ },
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "build:watch": "tsup --watch",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "lint": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json",
54
+ "prepublishOnly": "npm run build"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ },
59
+ "devDependencies": {
60
+ "@types/node": "^20.0.0",
61
+ "tsup": "^8.0.0",
62
+ "typescript": "^5.4.0",
63
+ "vitest": "^1.5.0"
64
+ }
65
+ }