@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/README.md +204 -0
- package/dist/index.d.mts +749 -0
- package/dist/index.d.ts +749 -0
- package/dist/index.js +745 -0
- package/dist/index.mjs +706 -0
- package/package.json +49 -0
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
|
+
});
|