@imerchantsolutions/sdk 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.js ADDED
@@ -0,0 +1,745 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ APIError: () => APIError,
24
+ AuthenticationError: () => AuthenticationError,
25
+ Customers: () => Customers,
26
+ NotFoundError: () => NotFoundError,
27
+ Payments: () => Payments,
28
+ RateLimitError: () => RateLimitError,
29
+ Refunds: () => Refunds,
30
+ Subscriptions: () => Subscriptions,
31
+ ValidationError: () => ValidationError,
32
+ WebhookSignatureError: () => WebhookSignatureError,
33
+ Webhooks: () => Webhooks,
34
+ iMerchant: () => iMerchant,
35
+ iMerchantError: () => iMerchantError
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/errors.ts
40
+ var iMerchantError = class _iMerchantError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = "iMerchantError";
44
+ Object.setPrototypeOf(this, _iMerchantError.prototype);
45
+ }
46
+ };
47
+ var APIError = class _APIError extends iMerchantError {
48
+ constructor(message, statusCode, code, details) {
49
+ super(message);
50
+ this.name = "APIError";
51
+ this.statusCode = statusCode;
52
+ this.code = code;
53
+ this.details = details;
54
+ Object.setPrototypeOf(this, _APIError.prototype);
55
+ }
56
+ };
57
+ var AuthenticationError = class _AuthenticationError extends iMerchantError {
58
+ constructor(message = "Invalid API key provided") {
59
+ super(message);
60
+ this.name = "AuthenticationError";
61
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
62
+ }
63
+ };
64
+ var NotFoundError = class _NotFoundError extends APIError {
65
+ constructor(resource, id) {
66
+ super(`${resource} with ID '${id}' not found`, 404, "not_found");
67
+ this.name = "NotFoundError";
68
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
69
+ }
70
+ };
71
+ var ValidationError = class _ValidationError extends APIError {
72
+ constructor(message, errors) {
73
+ super(message, 400, "validation_error", { errors });
74
+ this.name = "ValidationError";
75
+ this.errors = errors;
76
+ Object.setPrototypeOf(this, _ValidationError.prototype);
77
+ }
78
+ };
79
+ var RateLimitError = class _RateLimitError extends APIError {
80
+ constructor(retryAfter) {
81
+ super(
82
+ "Rate limit exceeded. Please slow down your requests.",
83
+ 429,
84
+ "rate_limit_exceeded",
85
+ { retryAfter }
86
+ );
87
+ this.name = "RateLimitError";
88
+ this.retryAfter = retryAfter;
89
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
90
+ }
91
+ };
92
+ var WebhookSignatureError = class _WebhookSignatureError extends iMerchantError {
93
+ constructor(message = "Invalid webhook signature") {
94
+ super(message);
95
+ this.name = "WebhookSignatureError";
96
+ Object.setPrototypeOf(this, _WebhookSignatureError.prototype);
97
+ }
98
+ };
99
+
100
+ // src/http.ts
101
+ var DEFAULT_BASE_URL = "https://api.imerchant.com";
102
+ var DEFAULT_TIMEOUT = 3e4;
103
+ var DEFAULT_API_VERSION = "v1";
104
+ var HttpClient = class {
105
+ constructor(config) {
106
+ if (!config.apiKey) {
107
+ throw new AuthenticationError("API key is required");
108
+ }
109
+ this.config = {
110
+ apiKey: config.apiKey,
111
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
112
+ timeout: config.timeout || DEFAULT_TIMEOUT,
113
+ apiVersion: config.apiVersion || DEFAULT_API_VERSION
114
+ };
115
+ }
116
+ getUrl(path) {
117
+ const baseUrl = this.config.baseUrl.replace(/\/$/, "");
118
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
119
+ return `${baseUrl}/${this.config.apiVersion}${cleanPath}`;
120
+ }
121
+ async handleResponse(response) {
122
+ const contentType = response.headers.get("content-type");
123
+ const isJson = contentType?.includes("application/json");
124
+ if (!response.ok) {
125
+ let errorData = {};
126
+ if (isJson) {
127
+ try {
128
+ const jsonData = await response.json();
129
+ errorData = jsonData;
130
+ } catch {
131
+ }
132
+ }
133
+ const message = errorData.message || `Request failed with status ${response.status}`;
134
+ const code = errorData.code || "unknown_error";
135
+ switch (response.status) {
136
+ case 401:
137
+ throw new AuthenticationError(message);
138
+ case 400:
139
+ if (errorData.errors) {
140
+ throw new ValidationError(message, errorData.errors);
141
+ }
142
+ throw new APIError(message, response.status, code);
143
+ case 429:
144
+ const retryAfter = response.headers.get("retry-after");
145
+ throw new RateLimitError(retryAfter ? parseInt(retryAfter, 10) : void 0);
146
+ default:
147
+ throw new APIError(message, response.status, code);
148
+ }
149
+ }
150
+ if (!isJson) {
151
+ return {};
152
+ }
153
+ return response.json();
154
+ }
155
+ async request(method, path, data, options) {
156
+ const url = this.getUrl(path);
157
+ const timeout = options?.timeout || this.config.timeout;
158
+ const headers = {
159
+ "Authorization": `Bearer ${this.config.apiKey}`,
160
+ "Content-Type": "application/json",
161
+ "X-SDK-Version": "1.0.0"
162
+ };
163
+ if (options?.idempotencyKey) {
164
+ headers["Idempotency-Key"] = options.idempotencyKey;
165
+ }
166
+ const controller = new AbortController();
167
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
168
+ try {
169
+ const response = await fetch(url, {
170
+ method,
171
+ headers,
172
+ body: data ? JSON.stringify(data) : void 0,
173
+ signal: controller.signal
174
+ });
175
+ return this.handleResponse(response);
176
+ } catch (error) {
177
+ if (error instanceof Error && error.name === "AbortError") {
178
+ throw new APIError("Request timed out", 408, "timeout");
179
+ }
180
+ throw error;
181
+ } finally {
182
+ clearTimeout(timeoutId);
183
+ }
184
+ }
185
+ get(path, options) {
186
+ return this.request("GET", path, void 0, options);
187
+ }
188
+ post(path, data, options) {
189
+ return this.request("POST", path, data, options);
190
+ }
191
+ put(path, data, options) {
192
+ return this.request("PUT", path, data, options);
193
+ }
194
+ patch(path, data, options) {
195
+ return this.request("PATCH", path, data, options);
196
+ }
197
+ delete(path, options) {
198
+ return this.request("DELETE", path, void 0, options);
199
+ }
200
+ };
201
+
202
+ // src/resources/payments.ts
203
+ var Payments = class {
204
+ constructor(http) {
205
+ this.http = http;
206
+ }
207
+ /**
208
+ * Create a new payment
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * const payment = await client.payments.create({
213
+ * amount: 1000, // $10.00 in cents
214
+ * currency: 'usd',
215
+ * reference: 'order_123',
216
+ * description: 'Test payment',
217
+ * });
218
+ * ```
219
+ */
220
+ async create(params, options) {
221
+ return this.http.post("/payments", params, options);
222
+ }
223
+ /**
224
+ * Retrieve a payment by ID
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * const payment = await client.payments.retrieve('pay_abc123');
229
+ * console.log(payment.status);
230
+ * ```
231
+ */
232
+ async retrieve(id, options) {
233
+ return this.http.get(`/payments/${id}`, options);
234
+ }
235
+ /**
236
+ * List all payments with optional filters
237
+ *
238
+ * @example
239
+ * ```typescript
240
+ * const { data, hasMore } = await client.payments.list({
241
+ * status: 'captured',
242
+ * limit: 10,
243
+ * });
244
+ * ```
245
+ */
246
+ async list(params, options) {
247
+ const searchParams = new URLSearchParams();
248
+ if (params?.limit) searchParams.set("limit", params.limit.toString());
249
+ if (params?.cursor) searchParams.set("cursor", params.cursor);
250
+ if (params?.status) searchParams.set("status", params.status);
251
+ if (params?.customerId) searchParams.set("customerId", params.customerId);
252
+ if (params?.createdAfter) searchParams.set("createdAfter", params.createdAfter);
253
+ if (params?.createdBefore) searchParams.set("createdBefore", params.createdBefore);
254
+ const query = searchParams.toString();
255
+ const path = query ? `/payments?${query}` : "/payments";
256
+ return this.http.get(path, options);
257
+ }
258
+ /**
259
+ * Capture an authorized payment
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * // Capture full amount
264
+ * const payment = await client.payments.capture('pay_abc123');
265
+ *
266
+ * // Capture partial amount
267
+ * const payment = await client.payments.capture('pay_abc123', { amount: 500 });
268
+ * ```
269
+ */
270
+ async capture(id, params, options) {
271
+ return this.http.post(`/payments/${id}/capture`, params || {}, options);
272
+ }
273
+ /**
274
+ * Cancel an authorized payment
275
+ *
276
+ * @example
277
+ * ```typescript
278
+ * const payment = await client.payments.cancel('pay_abc123');
279
+ * ```
280
+ */
281
+ async cancel(id, options) {
282
+ return this.http.post(`/payments/${id}/cancel`, {}, options);
283
+ }
284
+ };
285
+
286
+ // src/resources/refunds.ts
287
+ var Refunds = class {
288
+ constructor(http) {
289
+ this.http = http;
290
+ }
291
+ /**
292
+ * Create a refund for a payment
293
+ *
294
+ * @example
295
+ * ```typescript
296
+ * // Full refund
297
+ * const refund = await client.refunds.create({
298
+ * paymentId: 'pay_abc123',
299
+ * reason: 'Customer request',
300
+ * });
301
+ *
302
+ * // Partial refund
303
+ * const refund = await client.refunds.create({
304
+ * paymentId: 'pay_abc123',
305
+ * amount: 500, // $5.00 in cents
306
+ * reason: 'Partial return',
307
+ * });
308
+ * ```
309
+ */
310
+ async create(params, options) {
311
+ return this.http.post("/refunds", params, options);
312
+ }
313
+ /**
314
+ * Retrieve a refund by ID
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * const refund = await client.refunds.retrieve('ref_abc123');
319
+ * console.log(refund.status);
320
+ * ```
321
+ */
322
+ async retrieve(id, options) {
323
+ return this.http.get(`/refunds/${id}`, options);
324
+ }
325
+ /**
326
+ * List all refunds with optional filters
327
+ *
328
+ * @example
329
+ * ```typescript
330
+ * // List all refunds for a payment
331
+ * const { data } = await client.refunds.list({
332
+ * paymentId: 'pay_abc123',
333
+ * });
334
+ *
335
+ * // List refunds by status
336
+ * const { data } = await client.refunds.list({
337
+ * status: 'completed',
338
+ * limit: 20,
339
+ * });
340
+ * ```
341
+ */
342
+ async list(params, options) {
343
+ const searchParams = new URLSearchParams();
344
+ if (params?.limit) searchParams.set("limit", params.limit.toString());
345
+ if (params?.cursor) searchParams.set("cursor", params.cursor);
346
+ if (params?.paymentId) searchParams.set("paymentId", params.paymentId);
347
+ if (params?.status) searchParams.set("status", params.status);
348
+ const query = searchParams.toString();
349
+ const path = query ? `/refunds?${query}` : "/refunds";
350
+ return this.http.get(path, options);
351
+ }
352
+ };
353
+
354
+ // src/resources/customers.ts
355
+ var Customers = class {
356
+ constructor(http) {
357
+ this.http = http;
358
+ }
359
+ /**
360
+ * Create a new customer
361
+ *
362
+ * @example
363
+ * ```typescript
364
+ * const customer = await client.customers.create({
365
+ * email: 'john@example.com',
366
+ * firstName: 'John',
367
+ * lastName: 'Doe',
368
+ * metadata: { userId: '12345' },
369
+ * });
370
+ * ```
371
+ */
372
+ async create(params, options) {
373
+ return this.http.post("/customers", params, options);
374
+ }
375
+ /**
376
+ * Retrieve a customer by ID
377
+ *
378
+ * @example
379
+ * ```typescript
380
+ * const customer = await client.customers.retrieve('cus_abc123');
381
+ * console.log(customer.email);
382
+ * ```
383
+ */
384
+ async retrieve(id, options) {
385
+ return this.http.get(`/customers/${id}`, options);
386
+ }
387
+ /**
388
+ * Update a customer
389
+ *
390
+ * @example
391
+ * ```typescript
392
+ * const customer = await client.customers.update('cus_abc123', {
393
+ * firstName: 'Jane',
394
+ * phone: '+1234567890',
395
+ * });
396
+ * ```
397
+ */
398
+ async update(id, params, options) {
399
+ return this.http.patch(`/customers/${id}`, params, options);
400
+ }
401
+ /**
402
+ * Delete a customer
403
+ *
404
+ * @example
405
+ * ```typescript
406
+ * await client.customers.delete('cus_abc123');
407
+ * ```
408
+ */
409
+ async delete(id, options) {
410
+ await this.http.delete(`/customers/${id}`, options);
411
+ }
412
+ /**
413
+ * List all customers with optional filters
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * const { data, hasMore } = await client.customers.list({
418
+ * limit: 20,
419
+ * });
420
+ *
421
+ * // Search by email
422
+ * const { data } = await client.customers.list({
423
+ * email: 'john@example.com',
424
+ * });
425
+ * ```
426
+ */
427
+ async list(params, options) {
428
+ const searchParams = new URLSearchParams();
429
+ if (params?.limit) searchParams.set("limit", params.limit.toString());
430
+ if (params?.cursor) searchParams.set("cursor", params.cursor);
431
+ if (params?.email) searchParams.set("email", params.email);
432
+ if (params?.createdAfter) searchParams.set("createdAfter", params.createdAfter);
433
+ if (params?.createdBefore) searchParams.set("createdBefore", params.createdBefore);
434
+ const query = searchParams.toString();
435
+ const path = query ? `/customers?${query}` : "/customers";
436
+ return this.http.get(path, options);
437
+ }
438
+ };
439
+
440
+ // src/resources/subscriptions.ts
441
+ var Subscriptions = class {
442
+ constructor(http) {
443
+ this.http = http;
444
+ }
445
+ /**
446
+ * Create a new subscription
447
+ *
448
+ * @example
449
+ * ```typescript
450
+ * const subscription = await client.subscriptions.create({
451
+ * customerId: 'cus_abc123',
452
+ * amount: 2999, // $29.99 in cents
453
+ * currency: 'usd',
454
+ * interval: 'month',
455
+ * });
456
+ * ```
457
+ */
458
+ async create(params, options) {
459
+ return this.http.post("/subscriptions", params, options);
460
+ }
461
+ /**
462
+ * Retrieve a subscription by ID
463
+ *
464
+ * @example
465
+ * ```typescript
466
+ * const subscription = await client.subscriptions.retrieve('sub_abc123');
467
+ * console.log(subscription.status);
468
+ * ```
469
+ */
470
+ async retrieve(id, options) {
471
+ return this.http.get(`/subscriptions/${id}`, options);
472
+ }
473
+ /**
474
+ * Cancel a subscription
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * // Cancel immediately
479
+ * const subscription = await client.subscriptions.cancel('sub_abc123');
480
+ *
481
+ * // Cancel at end of billing period
482
+ * const subscription = await client.subscriptions.cancel('sub_abc123', {
483
+ * atPeriodEnd: true,
484
+ * });
485
+ * ```
486
+ */
487
+ async cancel(id, params, options) {
488
+ return this.http.post(
489
+ `/subscriptions/${id}/cancel`,
490
+ params || {},
491
+ options
492
+ );
493
+ }
494
+ /**
495
+ * Pause a subscription
496
+ *
497
+ * @example
498
+ * ```typescript
499
+ * const subscription = await client.subscriptions.pause('sub_abc123');
500
+ * ```
501
+ */
502
+ async pause(id, options) {
503
+ return this.http.post(`/subscriptions/${id}/pause`, {}, options);
504
+ }
505
+ /**
506
+ * Resume a paused subscription
507
+ *
508
+ * @example
509
+ * ```typescript
510
+ * const subscription = await client.subscriptions.resume('sub_abc123');
511
+ * ```
512
+ */
513
+ async resume(id, options) {
514
+ return this.http.post(`/subscriptions/${id}/resume`, {}, options);
515
+ }
516
+ /**
517
+ * List all subscriptions with optional filters
518
+ *
519
+ * @example
520
+ * ```typescript
521
+ * // List all active subscriptions
522
+ * const { data } = await client.subscriptions.list({
523
+ * status: 'active',
524
+ * });
525
+ *
526
+ * // List subscriptions for a customer
527
+ * const { data } = await client.subscriptions.list({
528
+ * customerId: 'cus_abc123',
529
+ * });
530
+ * ```
531
+ */
532
+ async list(params, options) {
533
+ const searchParams = new URLSearchParams();
534
+ if (params?.limit) searchParams.set("limit", params.limit.toString());
535
+ if (params?.cursor) searchParams.set("cursor", params.cursor);
536
+ if (params?.customerId) searchParams.set("customerId", params.customerId);
537
+ if (params?.status) searchParams.set("status", params.status);
538
+ const query = searchParams.toString();
539
+ const path = query ? `/subscriptions?${query}` : "/subscriptions";
540
+ return this.http.get(path, options);
541
+ }
542
+ };
543
+
544
+ // src/resources/webhooks.ts
545
+ var Webhooks = class {
546
+ /**
547
+ * Verify webhook signature and parse the event
548
+ *
549
+ * @example
550
+ * ```typescript
551
+ * // Express.js example
552
+ * app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
553
+ * const signature = req.headers['imerchant-signature'] as string;
554
+ *
555
+ * try {
556
+ * const event = iMerchant.webhooks.verify({
557
+ * body: req.body.toString(),
558
+ * signature,
559
+ * }, process.env.WEBHOOK_SECRET);
560
+ *
561
+ * switch (event.type) {
562
+ * case 'payment.captured':
563
+ * // Handle successful payment
564
+ * break;
565
+ * case 'payment.failed':
566
+ * // Handle failed payment
567
+ * break;
568
+ * }
569
+ *
570
+ * res.json({ received: true });
571
+ * } catch (err) {
572
+ * res.status(400).send('Invalid signature');
573
+ * }
574
+ * });
575
+ * ```
576
+ *
577
+ * @param payload - The webhook payload containing body and signature
578
+ * @param secret - Your webhook signing secret
579
+ * @returns The verified and parsed webhook event
580
+ * @throws {WebhookSignatureError} If signature verification fails
581
+ */
582
+ static verify(payload, secret) {
583
+ if (!payload.body || !payload.signature) {
584
+ throw new WebhookSignatureError("Missing payload body or signature");
585
+ }
586
+ if (!secret) {
587
+ throw new WebhookSignatureError("Webhook secret is required");
588
+ }
589
+ const parts = payload.signature.split(",");
590
+ const signatureParts = {};
591
+ for (const part of parts) {
592
+ const [key, value] = part.split("=");
593
+ if (key && value) {
594
+ signatureParts[key] = value;
595
+ }
596
+ }
597
+ const timestamp = signatureParts["t"];
598
+ const signature = signatureParts["v1"];
599
+ if (!timestamp || !signature) {
600
+ throw new WebhookSignatureError("Invalid signature format");
601
+ }
602
+ const timestampAge = Date.now() - parseInt(timestamp, 10) * 1e3;
603
+ if (timestampAge > 5 * 60 * 1e3) {
604
+ throw new WebhookSignatureError("Webhook timestamp too old");
605
+ }
606
+ const expectedSignature = this.computeSignature(
607
+ `${timestamp}.${payload.body}`,
608
+ secret
609
+ );
610
+ if (!this.secureCompare(signature, expectedSignature)) {
611
+ throw new WebhookSignatureError("Signature verification failed");
612
+ }
613
+ try {
614
+ return JSON.parse(payload.body);
615
+ } catch {
616
+ throw new WebhookSignatureError("Invalid JSON payload");
617
+ }
618
+ }
619
+ /**
620
+ * Compute HMAC-SHA256 signature
621
+ * Uses Node.js crypto module (available in Node.js environments)
622
+ */
623
+ static computeSignature(data, secret) {
624
+ if (typeof process !== "undefined" && process.versions?.node) {
625
+ try {
626
+ const requireFn = new Function("moduleName", "return require(moduleName)");
627
+ const cryptoModule = requireFn("crypto");
628
+ return cryptoModule.createHmac("sha256", secret).update(data).digest("hex");
629
+ } catch {
630
+ throw new WebhookSignatureError(
631
+ "Unable to load crypto module. Ensure you are running in Node.js."
632
+ );
633
+ }
634
+ }
635
+ throw new WebhookSignatureError(
636
+ "Synchronous signature verification is only available in Node.js. Use verifyAsync() in browser environments."
637
+ );
638
+ }
639
+ /**
640
+ * Timing-safe string comparison to prevent timing attacks
641
+ */
642
+ static secureCompare(a, b) {
643
+ if (a.length !== b.length) {
644
+ return false;
645
+ }
646
+ let result = 0;
647
+ for (let i = 0; i < a.length; i++) {
648
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
649
+ }
650
+ return result === 0;
651
+ }
652
+ /**
653
+ * Async signature verification using Web Crypto API
654
+ * Use this in environments that support Web Crypto
655
+ *
656
+ * @example
657
+ * ```typescript
658
+ * const event = await iMerchant.webhooks.verifyAsync(payload, secret);
659
+ * ```
660
+ */
661
+ static async verifyAsync(payload, secret) {
662
+ if (!payload.body || !payload.signature) {
663
+ throw new WebhookSignatureError("Missing payload body or signature");
664
+ }
665
+ if (!secret) {
666
+ throw new WebhookSignatureError("Webhook secret is required");
667
+ }
668
+ const parts = payload.signature.split(",");
669
+ const signatureParts = {};
670
+ for (const part of parts) {
671
+ const [key2, value] = part.split("=");
672
+ if (key2 && value) {
673
+ signatureParts[key2] = value;
674
+ }
675
+ }
676
+ const timestamp = signatureParts["t"];
677
+ const signature = signatureParts["v1"];
678
+ if (!timestamp || !signature) {
679
+ throw new WebhookSignatureError("Invalid signature format");
680
+ }
681
+ const timestampAge = Date.now() - parseInt(timestamp, 10) * 1e3;
682
+ if (timestampAge > 5 * 60 * 1e3) {
683
+ throw new WebhookSignatureError("Webhook timestamp too old");
684
+ }
685
+ const encoder = new TextEncoder();
686
+ const key = await crypto.subtle.importKey(
687
+ "raw",
688
+ encoder.encode(secret),
689
+ { name: "HMAC", hash: "SHA-256" },
690
+ false,
691
+ ["sign"]
692
+ );
693
+ const signatureData = encoder.encode(`${timestamp}.${payload.body}`);
694
+ const signatureBuffer = await crypto.subtle.sign("HMAC", key, signatureData);
695
+ const expectedSignature = Array.from(new Uint8Array(signatureBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
696
+ if (!this.secureCompare(signature, expectedSignature)) {
697
+ throw new WebhookSignatureError("Signature verification failed");
698
+ }
699
+ try {
700
+ return JSON.parse(payload.body);
701
+ } catch {
702
+ throw new WebhookSignatureError("Invalid JSON payload");
703
+ }
704
+ }
705
+ };
706
+
707
+ // src/client.ts
708
+ var iMerchant = class {
709
+ /**
710
+ * Create a new iMerchant client
711
+ *
712
+ * @param config - Client configuration
713
+ * @param config.apiKey - Your iMerchant API key (required)
714
+ * @param config.baseUrl - API base URL (optional, defaults to https://api.imerchant.com)
715
+ * @param config.timeout - Request timeout in ms (optional, defaults to 30000)
716
+ * @param config.apiVersion - API version (optional, defaults to 'v1')
717
+ */
718
+ constructor(config) {
719
+ this.http = new HttpClient(config);
720
+ this.payments = new Payments(this.http);
721
+ this.refunds = new Refunds(this.http);
722
+ this.customers = new Customers(this.http);
723
+ this.subscriptions = new Subscriptions(this.http);
724
+ }
725
+ };
726
+ /**
727
+ * Static webhook utilities for signature verification
728
+ */
729
+ iMerchant.webhooks = Webhooks;
730
+ // Annotate the CommonJS export names for ESM import in node:
731
+ 0 && (module.exports = {
732
+ APIError,
733
+ AuthenticationError,
734
+ Customers,
735
+ NotFoundError,
736
+ Payments,
737
+ RateLimitError,
738
+ Refunds,
739
+ Subscriptions,
740
+ ValidationError,
741
+ WebhookSignatureError,
742
+ Webhooks,
743
+ iMerchant,
744
+ iMerchantError
745
+ });