@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.
@@ -0,0 +1,483 @@
1
+ declare class BilliumError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ declare class BilliumWebhookSignatureError extends BilliumError {
5
+ constructor(message: string);
6
+ }
7
+ declare class BilliumWebhookTimestampError extends BilliumError {
8
+ constructor(message: string);
9
+ }
10
+ declare class BilliumApiError extends BilliumError {
11
+ readonly status: number;
12
+ readonly code?: string | undefined;
13
+ constructor(message: string, status: number, code?: string | undefined);
14
+ }
15
+
16
+ /**
17
+ * Single source of truth for the SDK version reported in the User-Agent
18
+ * header. **Keep this in sync with `package.json#version`** when bumping.
19
+ *
20
+ * Hardcoded rather than imported from package.json so that the build does
21
+ * not bundle the entire package.json blob (with dev/test config) into the
22
+ * production output.
23
+ */
24
+ declare const SDK_VERSION = "1.0.0";
25
+
26
+ interface HttpClientOptions {
27
+ /**
28
+ * Maximum number of retry attempts after the initial request fails.
29
+ * Total HTTP calls = maxRetries + 1. Set to 0 to disable retries entirely.
30
+ * @default 2
31
+ */
32
+ maxRetries?: number;
33
+ /**
34
+ * Initial backoff delay in milliseconds. Subsequent retries use
35
+ * exponential backoff with full jitter, capped at `maxDelayMs`.
36
+ * @default 500
37
+ */
38
+ baseDelayMs?: number;
39
+ /**
40
+ * Upper bound for the backoff delay between retries.
41
+ * @default 30000
42
+ */
43
+ maxDelayMs?: number;
44
+ }
45
+ /**
46
+ * Acceptable shape for query string params: any object whose values are
47
+ * primitives the URL spec can serialize. `undefined` and `null` values are
48
+ * dropped at serialization time (so callers can spread optional fields
49
+ * without filtering them first), and non-primitive values are skipped.
50
+ *
51
+ * Typed as `Readonly<Record<string, unknown>>` rather than a stricter union
52
+ * so user-defined interfaces with optional properties (e.g.
53
+ * `{ page?: number; limit?: number }`) can be passed directly without an
54
+ * `as Record<…>` cast — TypeScript's "weak type detection" otherwise
55
+ * refuses to widen interfaces with all-optional properties into a stricter
56
+ * value union.
57
+ */
58
+ type QueryParams = Readonly<Record<string, unknown>>;
59
+ interface RequestOptions {
60
+ body?: unknown;
61
+ params?: QueryParams;
62
+ headers?: Record<string, string>;
63
+ }
64
+ declare class HttpClient {
65
+ private readonly baseUrl;
66
+ private readonly apiKey;
67
+ private readonly isPublicKey;
68
+ private readonly maxRetries;
69
+ private readonly baseDelayMs;
70
+ private readonly maxDelayMs;
71
+ constructor(baseUrl: string, apiKey: string, options?: HttpClientOptions);
72
+ /**
73
+ * Throws a `BilliumError` if the configured key is a public key (`pk_*`).
74
+ *
75
+ * Used by methods that the backend won't accept a public key for —
76
+ * webhook management, invoice cancellation, anything that mutates state
77
+ * beyond invoice creation. Surfacing the error in the SDK rather than
78
+ * letting it round-trip to the backend gives developers a clear,
79
+ * actionable message instead of a generic `403 Insufficient permissions`.
80
+ *
81
+ * @param method - Dotted method name for the error message ("webhooks.create", etc.)
82
+ */
83
+ assertSecretKey(method: string): void;
84
+ get<T>(path: string, params?: object): Promise<T>;
85
+ post<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
86
+ put<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
87
+ patch<T>(path: string, body?: unknown, options?: RequestOptions): Promise<T>;
88
+ delete<T>(path: string, options?: RequestOptions): Promise<T>;
89
+ private request;
90
+ private headers;
91
+ private buildUrl;
92
+ private parse;
93
+ /**
94
+ * Returns the number of milliseconds to wait before the next retry.
95
+ *
96
+ * Honors a `Retry-After` header on the response when present (parsed as
97
+ * either a delta-seconds integer or an HTTP date). Otherwise falls back
98
+ * to exponential backoff with full jitter — randomizing across the entire
99
+ * `[0, exponentialCeiling]` range to avoid retry storms when many
100
+ * clients fail simultaneously.
101
+ */
102
+ private computeDelay;
103
+ private sleep;
104
+ }
105
+
106
+ type WebhookEventType = 'invoice.*' | 'invoice.created' | 'invoice.updated' | 'invoice.paid' | 'invoice.underpaid' | 'invoice.overpaid' | 'invoice.expired' | 'invoice.cancelled' | 'payment.*' | 'payment.created' | 'payment.updated' | 'payment.detected' | 'payment.confirmed' | 'payment.paid' | 'payment.underpaid' | 'payment.overpaid' | 'payment.expired';
107
+ interface WebhookSecret {
108
+ id: string;
109
+ webhookId: string;
110
+ secretKeyPreview: string;
111
+ isActive: boolean;
112
+ expiresAt?: string;
113
+ }
114
+ interface Webhook {
115
+ id: string;
116
+ merchantId: string;
117
+ url: string;
118
+ events: WebhookEventType[];
119
+ isActive: boolean;
120
+ description?: string;
121
+ /** Number of retry attempts on failure (0–10). */
122
+ retryCount: number;
123
+ /** Request timeout in milliseconds (1000–30000). */
124
+ timeout: number;
125
+ webhookSecrets: WebhookSecret[];
126
+ }
127
+ interface CreateWebhookParams {
128
+ /** The HTTPS URL Billium will POST events to. */
129
+ url: string;
130
+ /** List of event types to subscribe to. Use `'invoice.*'` or `'payment.*'` to subscribe to all events in a category. */
131
+ events: WebhookEventType[];
132
+ /** Optional description for this endpoint. */
133
+ description?: string;
134
+ /** Whether the webhook is active on creation. Defaults to `true`. */
135
+ isActive?: boolean;
136
+ /** Number of retry attempts on delivery failure (0–10). Defaults to `3`. */
137
+ retryCount?: number;
138
+ /** Request timeout in milliseconds (1000–30000). Defaults to `30000`. */
139
+ timeout?: number;
140
+ }
141
+ type UpdateWebhookParams = Partial<CreateWebhookParams>;
142
+ interface WebhookEvent {
143
+ event: string;
144
+ id: string;
145
+ data: unknown;
146
+ timestamp: string;
147
+ }
148
+ interface VerifyOptions {
149
+ /**
150
+ * Maximum allowed age of the webhook timestamp in seconds.
151
+ * Set to 0 to disable the check.
152
+ * @default 300
153
+ */
154
+ tolerance?: number;
155
+ }
156
+ declare class WebhooksClient {
157
+ private readonly defaultSecret?;
158
+ private readonly http?;
159
+ private readonly basePath;
160
+ constructor(defaultSecret?: string | undefined, http?: HttpClient | undefined, merchantId?: string);
161
+ /**
162
+ * Parses and verifies an incoming Billium webhook request.
163
+ *
164
+ * Signature header format: x-signature: t={unix_seconds},v1={hmac_sha256_hex}
165
+ * Signed string: "{unix_seconds}.{raw_body}"
166
+ * Algorithm: HMAC-SHA256
167
+ *
168
+ * If `webhookSecret` was passed to the `Billium` constructor, you can omit
169
+ * the `secret` argument here. Pass it explicitly to override.
170
+ *
171
+ * @param rawBody Raw request body as Buffer or string — must not be parsed
172
+ * @param signature Value of the `x-signature` header
173
+ * @param secret Webhook secret — can be omitted if set in the constructor
174
+ * @param options Optional config (tolerance window)
175
+ *
176
+ * @throws {BilliumError} No secret available
177
+ * @throws {BilliumWebhookSignatureError} Header malformed or HMAC mismatch
178
+ * @throws {BilliumWebhookTimestampError} Timestamp outside tolerance window
179
+ */
180
+ verify(rawBody: Buffer | string, signature: string, secret?: string, options?: VerifyOptions): WebhookEvent;
181
+ /**
182
+ * Creates a new webhook endpoint for the merchant.
183
+ *
184
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
185
+ * must be a **secret key** (`sk_*`) — public keys do not have webhook
186
+ * management scope.
187
+ */
188
+ create(params: CreateWebhookParams): Promise<Webhook>;
189
+ /**
190
+ * Lists all webhook endpoints for the merchant.
191
+ *
192
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
193
+ * must be a secret key (`sk_*`).
194
+ */
195
+ list(): Promise<Webhook[]>;
196
+ /**
197
+ * Updates a webhook endpoint.
198
+ *
199
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
200
+ * must be a secret key (`sk_*`).
201
+ */
202
+ update(webhookId: string, params: UpdateWebhookParams): Promise<Webhook>;
203
+ /**
204
+ * Deletes a webhook endpoint.
205
+ *
206
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
207
+ * must be a secret key (`sk_*`).
208
+ */
209
+ delete(webhookId: string): Promise<void>;
210
+ /**
211
+ * Sends a test ping to a webhook endpoint to verify it is reachable.
212
+ *
213
+ * Requires `apiKey` and `merchantId` in the constructor, and the `apiKey`
214
+ * must be a secret key (`sk_*`).
215
+ */
216
+ ping(webhookId: string): Promise<void>;
217
+ private managementHttp;
218
+ private managementPath;
219
+ private verifyInternal;
220
+ private parseSignatureHeader;
221
+ private timingSafeCompare;
222
+ }
223
+
224
+ type InvoiceStatus = 'AWAITING_PAYMENT' | 'PENDING_CONFIRMATION' | 'PAID' | 'UNDERPAID' | 'OVERPAID' | 'EXPIRED' | 'CANCELLED';
225
+ /**
226
+ * Customer attached to an invoice. Returned as a nested relation, not as
227
+ * flat `customerEmail` / `customerName` fields, because that mirrors the
228
+ * underlying data model: a customer is a separate entity that can be linked
229
+ * to multiple invoices.
230
+ */
231
+ interface InvoiceCustomer {
232
+ id: string;
233
+ merchantId: string;
234
+ email: string;
235
+ name: string | null;
236
+ address: string | null;
237
+ phoneNumber: string | null;
238
+ createdAt: string;
239
+ updatedAt: string;
240
+ }
241
+ /**
242
+ * Product an invoice was generated from, when applicable. Only set for
243
+ * invoices created via the public product checkout endpoint.
244
+ */
245
+ interface InvoiceProduct {
246
+ id: string;
247
+ name: string;
248
+ description: string | null;
249
+ price: string;
250
+ currency: string;
251
+ image: string | null;
252
+ }
253
+ /**
254
+ * A single transition in an invoice's status history. Useful for rendering
255
+ * a timeline in a merchant dashboard.
256
+ */
257
+ interface InvoiceTimelineEntry {
258
+ id: string;
259
+ invoiceId: string;
260
+ paymentStatus: InvoiceStatus;
261
+ time: string;
262
+ }
263
+ /**
264
+ * On-chain payment received against an invoice. An invoice can have zero
265
+ * payments (still awaiting), one (typical), or many (split payments,
266
+ * overpayments, etc.).
267
+ */
268
+ interface InvoicePayment {
269
+ id: string;
270
+ invoiceId: string;
271
+ chainId: number | null;
272
+ /** Symbol of the chain's native coin (e.g. 'BTC', 'ETH'). */
273
+ network: string | null;
274
+ txHash: string | null;
275
+ fromAddress: string | null;
276
+ toAddress: string | null;
277
+ amount: string;
278
+ currency: string;
279
+ status: string;
280
+ receivedTxAt: string | null;
281
+ confirmedTxAt: string | null;
282
+ createdAt: string;
283
+ updatedAt: string;
284
+ }
285
+ /**
286
+ * An invoice as returned by the Billium merchant API.
287
+ *
288
+ * **Note on numeric fields:** `rawAmount` and `endAmount` are strings, not
289
+ * numbers, because they're stored as `Decimal(15, 6)` in the database and
290
+ * serialized as strings to preserve precision. Use a decimal library
291
+ * (e.g. `decimal.js`) for arithmetic.
292
+ *
293
+ * **Note on relations:** `customer`, `product`, `payments`, and
294
+ * `invoiceTimeline` are always present on responses from `create()`,
295
+ * `get()`, `list()`, and `cancel()`. They will be `null` / `[]` when
296
+ * empty, never `undefined`.
297
+ */
298
+ interface Invoice {
299
+ id: string;
300
+ merchantId: string;
301
+ customerId: string | null;
302
+ productId: string | null;
303
+ name: string;
304
+ description: string | null;
305
+ redirectUrl: string | null;
306
+ /** Decimal serialized as string. The amount the merchant set on the invoice. */
307
+ rawAmount: string;
308
+ /** Decimal serialized as string. The amount the customer actually pays (rawAmount + fees, when applicable). */
309
+ endAmount: string;
310
+ currency: string;
311
+ status: InvoiceStatus;
312
+ expiresAt: string | null;
313
+ createdAt: string;
314
+ updatedAt: string;
315
+ customer: InvoiceCustomer | null;
316
+ product: InvoiceProduct | null;
317
+ payments: InvoicePayment[];
318
+ invoiceTimeline: InvoiceTimelineEntry[];
319
+ }
320
+ interface PaginatedResult<T> {
321
+ data: T[];
322
+ total: number;
323
+ page: number;
324
+ limit: number;
325
+ }
326
+ interface CreateInvoiceParams {
327
+ /** Invoice display name. */
328
+ name: string;
329
+ /** Amount in the specified currency. */
330
+ rawAmount: number;
331
+ /** Currency code (e.g. 'USD'). Defaults to 'USD'. */
332
+ currency?: string;
333
+ /** Customer email address. */
334
+ customerEmail?: string;
335
+ /** Customer full name. */
336
+ customerName?: string;
337
+ /** Customer billing address. */
338
+ customerAddress?: string;
339
+ /** Customer phone number. */
340
+ customerPhoneNumber?: string;
341
+ /** Optional description shown on the invoice. */
342
+ description?: string;
343
+ /** URL to redirect the customer after payment. */
344
+ redirectUrl?: string;
345
+ }
346
+ interface ListInvoicesParams {
347
+ /** Page number (1-based). Defaults to 1. */
348
+ page?: number;
349
+ /** Number of results per page (max 100). Defaults to 10. */
350
+ limit?: number;
351
+ /** Search by invoice name, description, or ID. */
352
+ search?: string;
353
+ }
354
+ /**
355
+ * Per-call options accepted by `invoices.create()`.
356
+ */
357
+ interface CreateInvoiceOptions {
358
+ /**
359
+ * A unique key (≤ 255 chars, scoped to your merchant) that the server uses
360
+ * to deduplicate retried requests. If you call `create()` twice with the
361
+ * same key and same body within 24 hours, the second call returns the
362
+ * exact response from the first — no duplicate invoice is created.
363
+ *
364
+ * **You should set this on every `create()` call** in production. The SDK
365
+ * also requires it for transparent retries on POST: without this header,
366
+ * the SDK will not retry a failed `create()` call (even on `503`),
367
+ * because it cannot prove the server didn't already accept the first
368
+ * attempt.
369
+ *
370
+ * Generate a fresh key per logical operation (e.g. one per checkout
371
+ * session, one per cart submission). A UUID v4 is a good default:
372
+ *
373
+ * ```typescript
374
+ * import { randomUUID } from 'crypto';
375
+ *
376
+ * const invoice = await billium.invoices.create(
377
+ * { name: 'Order #1234', rawAmount: 99.99 },
378
+ * { idempotencyKey: randomUUID() },
379
+ * );
380
+ * ```
381
+ *
382
+ * If the server sees the same key with a *different* body, it returns
383
+ * `409 Conflict` — that's a programming bug, not a transient error.
384
+ */
385
+ idempotencyKey?: string;
386
+ }
387
+ declare class InvoicesClient {
388
+ private readonly http;
389
+ private readonly basePath;
390
+ constructor(http: HttpClient, merchantId: string);
391
+ /**
392
+ * Creates a new invoice for the merchant.
393
+ *
394
+ * Pass `options.idempotencyKey` for safe retries — see
395
+ * {@link CreateInvoiceOptions.idempotencyKey} for the full rationale.
396
+ */
397
+ create(params: CreateInvoiceParams, options?: CreateInvoiceOptions): Promise<Invoice>;
398
+ /**
399
+ * Retrieves a single invoice by ID.
400
+ */
401
+ get(invoiceId: string): Promise<Invoice>;
402
+ /**
403
+ * Lists invoices for the merchant with optional pagination and search.
404
+ */
405
+ list(params?: ListInvoicesParams): Promise<PaginatedResult<Invoice>>;
406
+ /**
407
+ * Cancels an invoice. Only invoices in non-terminal states can be cancelled.
408
+ * Terminal states: PAID, UNDERPAID, OVERPAID, EXPIRED, CANCELLED.
409
+ *
410
+ * **Requires a secret key (`sk_*`).** Public keys (`pk_*`) cannot mutate
411
+ * existing invoices.
412
+ */
413
+ cancel(invoiceId: string): Promise<Invoice>;
414
+ }
415
+
416
+ interface BilliumOptions {
417
+ /**
418
+ * Your Billium **secret** API key (`sk_*`). Required to use
419
+ * `billium.invoices` and `billium.webhooks` management methods.
420
+ *
421
+ * Generate a key in the Billium dashboard under Settings → Developer →
422
+ * API keys. The dashboard issues two key types — public (`pk_*`) and
423
+ * secret (`sk_*`) — but the Node SDK only consumes secret keys, since
424
+ * every method it exposes mutates state or accesses webhook management
425
+ * endpoints that public keys aren't authorized for. Public keys are
426
+ * reserved for browser-side SDKs (vanilla JS, React, etc.) which will
427
+ * ship as separate packages.
428
+ *
429
+ * Passing a `pk_*` key here is allowed, but methods like
430
+ * `webhooks.create()` and `invoices.cancel()` will throw a
431
+ * `BilliumError` immediately rather than round-tripping a 403.
432
+ */
433
+ apiKey?: string;
434
+ /**
435
+ * Your merchant ID (mer_...).
436
+ * Required to use `billium.invoices`.
437
+ */
438
+ merchantId?: string;
439
+ /**
440
+ * Your webhook secret (whsec_...).
441
+ * When set, you can call `billium.webhooks.verify(body, signature)` without
442
+ * passing the secret as a third argument on every call.
443
+ */
444
+ webhookSecret?: string;
445
+ /**
446
+ * Override the API base URL. Useful for self-hosted deployments or testing.
447
+ * @default 'https://api.billium.to'
448
+ */
449
+ baseUrl?: string;
450
+ /**
451
+ * Maximum number of retry attempts for failed requests.
452
+ * Total HTTP calls = `maxRetries + 1`. Set to 0 to disable retries.
453
+ *
454
+ * Retries fire on network errors, 5xx responses, and 429 (rate limited).
455
+ * `Retry-After` headers from the server are honored when present.
456
+ *
457
+ * `GET`, `PUT`, `PATCH`, and `DELETE` are always retried when
458
+ * `maxRetries > 0`. `POST` is retried only when an `Idempotency-Key`
459
+ * header is set on the request — otherwise retrying could create
460
+ * duplicate resources.
461
+ *
462
+ * @default 2
463
+ */
464
+ maxRetries?: number;
465
+ /**
466
+ * Initial backoff delay in milliseconds. Retries use exponential backoff
467
+ * with full jitter, capped at `maxDelayMs`.
468
+ * @default 500
469
+ */
470
+ baseDelayMs?: number;
471
+ /**
472
+ * Upper bound for the backoff delay between retries.
473
+ * @default 30000
474
+ */
475
+ maxDelayMs?: number;
476
+ }
477
+ declare class Billium {
478
+ readonly webhooks: WebhooksClient;
479
+ readonly invoices: InvoicesClient;
480
+ constructor(options?: BilliumOptions);
481
+ }
482
+
483
+ export { Billium, BilliumApiError, BilliumError, type BilliumOptions, BilliumWebhookSignatureError, BilliumWebhookTimestampError, type CreateInvoiceOptions, type CreateInvoiceParams, type CreateWebhookParams, type Invoice, type InvoiceCustomer, type InvoicePayment, type InvoiceProduct, type InvoiceStatus, type InvoiceTimelineEntry, type ListInvoicesParams, type PaginatedResult, SDK_VERSION, type UpdateWebhookParams, type VerifyOptions, type Webhook, type WebhookEvent, type WebhookEventType, type WebhookSecret };