xendit-fn 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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +442 -0
  3. package/lib/index.cjs +1588 -0
  4. package/lib/index.d.cts +9574 -0
  5. package/lib/index.d.ts +7 -0
  6. package/lib/index.d.ts.map +1 -0
  7. package/lib/index.esm.d.ts +9574 -0
  8. package/lib/index.esm.js +1544 -0
  9. package/lib/sdk/axios.d.ts +3 -0
  10. package/lib/sdk/axios.d.ts.map +1 -0
  11. package/lib/sdk/card/index.d.ts +2 -0
  12. package/lib/sdk/card/index.d.ts.map +1 -0
  13. package/lib/sdk/card/schema.d.ts +586 -0
  14. package/lib/sdk/card/schema.d.ts.map +1 -0
  15. package/lib/sdk/common.d.ts +28 -0
  16. package/lib/sdk/common.d.ts.map +1 -0
  17. package/lib/sdk/customer/index.d.ts +7 -0
  18. package/lib/sdk/customer/index.d.ts.map +1 -0
  19. package/lib/sdk/customer/schema.d.ts +5933 -0
  20. package/lib/sdk/customer/schema.d.ts.map +1 -0
  21. package/lib/sdk/ewallet/create.d.ts +173 -0
  22. package/lib/sdk/ewallet/create.d.ts.map +1 -0
  23. package/lib/sdk/ewallet/schema.d.ts +783 -0
  24. package/lib/sdk/ewallet/schema.d.ts.map +1 -0
  25. package/lib/sdk/index.d.ts +2201 -0
  26. package/lib/sdk/index.d.ts.map +1 -0
  27. package/lib/sdk/invoice/index.d.ts +8 -0
  28. package/lib/sdk/invoice/index.d.ts.map +1 -0
  29. package/lib/sdk/invoice/schema.d.ts +2198 -0
  30. package/lib/sdk/invoice/schema.d.ts.map +1 -0
  31. package/lib/sdk/payment-method/index.d.ts +7 -0
  32. package/lib/sdk/payment-method/index.d.ts.map +1 -0
  33. package/lib/sdk/payment-method/schema.d.ts +990 -0
  34. package/lib/sdk/payment-method/schema.d.ts.map +1 -0
  35. package/lib/utils/errors.d.ts +58 -0
  36. package/lib/utils/errors.d.ts.map +1 -0
  37. package/lib/utils/index.d.ts +6 -0
  38. package/lib/utils/index.d.ts.map +1 -0
  39. package/lib/utils/pagination.d.ts +134 -0
  40. package/lib/utils/pagination.d.ts.map +1 -0
  41. package/lib/utils/rate-limit.d.ts +90 -0
  42. package/lib/utils/rate-limit.d.ts.map +1 -0
  43. package/lib/utils/type-guards.d.ts +13 -0
  44. package/lib/utils/type-guards.d.ts.map +1 -0
  45. package/lib/utils/webhook.d.ts +101 -0
  46. package/lib/utils/webhook.d.ts.map +1 -0
  47. package/package.json +83 -0
@@ -0,0 +1,1544 @@
1
+ import axios from 'axios';
2
+ import { z } from 'zod';
3
+ import { createHmac } from 'crypto';
4
+
5
+ const createAxiosInstance = (config)=>axios.create({
6
+ ...config,
7
+ headers: {
8
+ Accept: "application/json",
9
+ "Content-Type": "application/json",
10
+ common: {
11
+ Accept: "application/json",
12
+ "Content-Type": "application/json"
13
+ },
14
+ ...config?.headers
15
+ },
16
+ baseURL: "https://api.xendit.co"
17
+ });
18
+
19
+ /**
20
+ * Rate limiter implementation using token bucket algorithm
21
+ */ class RateLimiter {
22
+ /**
23
+ * Refill tokens based on elapsed time
24
+ */ refillTokens() {
25
+ const now = Date.now();
26
+ const timePassed = now - this.lastRefill;
27
+ const tokensToAdd = timePassed / this.config.windowMs * this.config.maxRequests;
28
+ this.tokens = Math.min(this.config.maxRequests, this.tokens + tokensToAdd);
29
+ this.lastRefill = now;
30
+ }
31
+ /**
32
+ * Check if a request can be made
33
+ */ canMakeRequest() {
34
+ this.refillTokens();
35
+ return this.tokens >= 1;
36
+ }
37
+ /**
38
+ * Consume a token for a request
39
+ */ consumeToken() {
40
+ this.refillTokens();
41
+ if (this.tokens >= 1) {
42
+ this.tokens -= 1;
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * Get time until next token is available
49
+ */ getWaitTime() {
50
+ if (this.canMakeRequest()) {
51
+ return 0;
52
+ }
53
+ const tokensNeeded = 1 - this.tokens;
54
+ return tokensNeeded / this.config.maxRequests * this.config.windowMs;
55
+ }
56
+ /**
57
+ * Wait for a token to become available
58
+ */ async waitForToken() {
59
+ const waitTime = this.getWaitTime();
60
+ if (waitTime > 0) {
61
+ await this.sleep(waitTime);
62
+ }
63
+ }
64
+ /**
65
+ * Sleep for specified milliseconds
66
+ */ sleep(ms) {
67
+ return new Promise((resolve)=>setTimeout(resolve, ms));
68
+ }
69
+ constructor(config = {}){
70
+ this.config = {
71
+ maxRequests: config.maxRequests ?? 100,
72
+ windowMs: config.windowMs ?? 60000,
73
+ requestDelayMs: config.requestDelayMs ?? 0,
74
+ maxRetries: config.maxRetries ?? 3,
75
+ baseRetryDelayMs: config.baseRetryDelayMs ?? 1000,
76
+ maxRetryDelayMs: config.maxRetryDelayMs ?? 30000
77
+ };
78
+ this.tokens = this.config.maxRequests;
79
+ this.lastRefill = Date.now();
80
+ }
81
+ }
82
+ /**
83
+ * Axios interceptor for rate limiting
84
+ */ function createRateLimitInterceptor(rateLimiter) {
85
+ return {
86
+ request: async (config)=>{
87
+ // Wait for token availability
88
+ await rateLimiter.waitForToken();
89
+ // Consume token
90
+ rateLimiter.consumeToken();
91
+ // Apply request delay if configured
92
+ const delay = rateLimiter.config.requestDelayMs;
93
+ if (delay > 0) {
94
+ await rateLimiter.sleep(delay);
95
+ }
96
+ return config;
97
+ },
98
+ response: (response)=>{
99
+ // Check for rate limit headers and adjust if needed
100
+ const rateLimitRemaining = response.headers["x-ratelimit-remaining"];
101
+ if (rateLimitRemaining !== undefined && Number(rateLimitRemaining) === 0) {
102
+ console.warn("Rate limit reached, requests will be throttled");
103
+ }
104
+ return response;
105
+ },
106
+ responseError: async (error)=>{
107
+ // Handle rate limit errors (HTTP 429)
108
+ if (error.response?.status === 429) {
109
+ const retryAfter = error.response.headers["retry-after"];
110
+ const delay = retryAfter ? Number(retryAfter) * 1000 : rateLimiter.config.baseRetryDelayMs;
111
+ console.warn(`Rate limited, retrying after ${delay}ms`);
112
+ await rateLimiter.sleep(delay);
113
+ // Don't automatically retry here, let the retry interceptor handle it
114
+ return Promise.reject(error);
115
+ }
116
+ return Promise.reject(error);
117
+ }
118
+ };
119
+ }
120
+ /**
121
+ * Basic retry interceptor - simplified for compatibility
122
+ */ function createRetryInterceptor(_config = {}) {
123
+ return async (error)=>{
124
+ // For now, just log retryable errors and reject
125
+ // This can be enhanced in the future with proper retry logic
126
+ if (isRetryableError(error)) {
127
+ console.warn("Request failed with retryable error:", error.message);
128
+ }
129
+ return Promise.reject(error);
130
+ };
131
+ }
132
+ /**
133
+ * Check if an error is retryable
134
+ */ function isRetryableError(error) {
135
+ // No response means network error, which is retryable
136
+ if (!error.response) {
137
+ return true;
138
+ }
139
+ // Specific status codes that are retryable
140
+ const retryableStatusCodes = [
141
+ 408,
142
+ 429,
143
+ 500,
144
+ 502,
145
+ 503,
146
+ 504
147
+ ];
148
+ return retryableStatusCodes.includes(error.response.status);
149
+ }
150
+ /**
151
+ * Setup rate limiting and retry logic for an Axios instance
152
+ */ function setupRateLimit(axiosInstance, config = {}) {
153
+ const rateLimiter = new RateLimiter(config);
154
+ const rateLimitInterceptor = createRateLimitInterceptor(rateLimiter);
155
+ const retryInterceptor = createRetryInterceptor(config);
156
+ // Add request interceptor for rate limiting
157
+ axiosInstance.interceptors.request.use(rateLimitInterceptor.request, (error)=>Promise.reject(error));
158
+ // Add response interceptors
159
+ axiosInstance.interceptors.response.use(rateLimitInterceptor.response, rateLimitInterceptor.responseError);
160
+ // Add retry interceptor (should be last)
161
+ axiosInstance.interceptors.response.use((response)=>response, retryInterceptor);
162
+ }
163
+ /**
164
+ * Create a rate-limited axios instance
165
+ */ function createRateLimitedAxios(baseURL, apiKey, config = {}) {
166
+ const axiosInstance = axios.create({
167
+ baseURL,
168
+ headers: {
169
+ Authorization: `Basic ${Buffer.from(`${apiKey}:`).toString("base64")}`,
170
+ "Content-Type": "application/json"
171
+ }
172
+ });
173
+ setupRateLimit(axiosInstance, config);
174
+ return axiosInstance;
175
+ }
176
+
177
+ const XenditErrorSchema = z.object({
178
+ error_code: z.string(),
179
+ message: z.string(),
180
+ errors: z.array(z.object({
181
+ field: z.string().optional(),
182
+ message: z.string()
183
+ })).optional()
184
+ });
185
+ class XenditApiError extends Error {
186
+ constructor(message, code, statusCode, details){
187
+ super(message);
188
+ this.name = "XenditApiError";
189
+ this.code = code;
190
+ this.statusCode = statusCode;
191
+ this.details = details;
192
+ // Maintain proper stack trace for where our error was thrown
193
+ if (Error.captureStackTrace) {
194
+ Error.captureStackTrace(this, XenditApiError);
195
+ }
196
+ }
197
+ }
198
+ class ValidationError extends Error {
199
+ constructor(message, validationErrors, field){
200
+ super(message);
201
+ this.name = "ValidationError";
202
+ this.field = field;
203
+ this.validationErrors = validationErrors;
204
+ if (Error.captureStackTrace) {
205
+ Error.captureStackTrace(this, ValidationError);
206
+ }
207
+ }
208
+ }
209
+ class AuthenticationError extends XenditApiError {
210
+ constructor(message = "Authentication failed"){
211
+ super(message, "AUTHENTICATION_ERROR", 401);
212
+ this.name = "AuthenticationError";
213
+ }
214
+ }
215
+ class NotFoundError extends XenditApiError {
216
+ constructor(message = "Resource not found"){
217
+ super(message, "NOT_FOUND_ERROR", 404);
218
+ this.name = "NotFoundError";
219
+ }
220
+ }
221
+ class RateLimitError extends XenditApiError {
222
+ constructor(message = "Rate limit exceeded"){
223
+ super(message, "RATE_LIMIT_ERROR", 429);
224
+ this.name = "RateLimitError";
225
+ }
226
+ }
227
+ const handleAxiosError = (error)=>{
228
+ if (error.response?.data) {
229
+ const parsed = XenditErrorSchema.safeParse(error.response.data);
230
+ if (parsed.success) {
231
+ throw new XenditApiError(parsed.data.message, parsed.data.error_code, error.response.status, parsed.data.errors ? {
232
+ errors: parsed.data.errors
233
+ } : undefined);
234
+ }
235
+ }
236
+ throw new XenditApiError(error.message || "Unknown API error", error.code || "UNKNOWN_ERROR", error.response?.status, error.response?.data);
237
+ };
238
+ const validateInput = (schema, data, fieldName)=>{
239
+ const result = schema.safeParse(data);
240
+ if (!result.success) {
241
+ throw new ValidationError(`Validation failed${fieldName ? ` for field ${fieldName}` : ""}`, result.error.issues, fieldName);
242
+ }
243
+ return result.data;
244
+ };
245
+
246
+ const PhoneSchema = z.string().min(7).max(15).refine((value)=>value.startsWith("+"));
247
+ const CountrySchema = z.union([
248
+ z.literal("PH"),
249
+ z.literal("ID"),
250
+ z.literal("MY"),
251
+ z.literal("TH"),
252
+ z.literal("VN")
253
+ ]);
254
+ const CurrencySchema = z.union([
255
+ z.literal("PHP"),
256
+ z.literal("IDR"),
257
+ z.literal("MYR"),
258
+ z.literal("THB"),
259
+ z.literal("VND")
260
+ ]);
261
+ z.object({
262
+ given_names: z.string(),
263
+ surname: z.string().optional(),
264
+ email: z.string().email().optional(),
265
+ mobile_number: PhoneSchema.optional(),
266
+ phone_number: PhoneSchema.optional()
267
+ });
268
+
269
+ z.union([
270
+ z.literal("INDIVIDUAL"),
271
+ z.literal("BUSINESS")
272
+ ]);
273
+ const IndividualDetailSchema = z.object({
274
+ given_names: z.string(),
275
+ surname: z.string().optional(),
276
+ nationality: z.string().optional(),
277
+ place_of_birth: z.string().optional(),
278
+ date_of_birth: z.string().optional(),
279
+ gender: z.union([
280
+ z.literal("MALE"),
281
+ z.literal("FEMALE"),
282
+ z.literal("OTHER")
283
+ ]).optional(),
284
+ employment: z.object({
285
+ employer_name: z.string(),
286
+ nature_of_business: z.string(),
287
+ role_description: z.string()
288
+ }).optional()
289
+ });
290
+ z.union([
291
+ z.literal("CORPORATION"),
292
+ z.literal("SOLE_PROPRIETOR"),
293
+ z.literal("PARTNERSHIP"),
294
+ z.literal("COOPERATIVE"),
295
+ z.literal("TRUST"),
296
+ z.literal("NON_PROFIT"),
297
+ z.literal("GOVERNMENT")
298
+ ]);
299
+ const BusinessDetailSchema = z.object({
300
+ business_name: z.string(),
301
+ trading_name: z.string().optional(),
302
+ business_type: z.string()
303
+ });
304
+ const AddressSchema = z.object({
305
+ street_line1: z.string().nullable().optional(),
306
+ street_line2: z.string().nullable().optional(),
307
+ city: z.string().nullable().optional(),
308
+ province_state: z.string().nullable().optional(),
309
+ postal_code: z.string().nullable().optional(),
310
+ country: z.string(),
311
+ category: z.string().nullable().optional(),
312
+ is_primary: z.boolean().nullable().optional()
313
+ });
314
+ const AccountTypeSchema = z.union([
315
+ z.literal("BANK_ACCOUNT"),
316
+ z.literal("EWALLET"),
317
+ z.literal("CREDIT_CARD"),
318
+ z.literal("PAY_LATER"),
319
+ z.literal("OTC"),
320
+ z.literal("QR_CODE"),
321
+ z.literal("SOCIAL_MEDIA")
322
+ ]);
323
+ const BankAccountSchema = z.object({
324
+ account_number: z.string(),
325
+ account_holder_name: z.string(),
326
+ swift_code: z.string().optional(),
327
+ account_type: z.string().optional(),
328
+ account_details: z.string().optional(),
329
+ currency: z.string().optional()
330
+ });
331
+ const EWalletAccountSchema = z.object({
332
+ account_number: z.string(),
333
+ account_holder_name: z.string(),
334
+ currency: z.string().optional()
335
+ });
336
+ const CreditCardAccountSchema = z.object({
337
+ token_id: z.string()
338
+ });
339
+ const OTCAccountSchema = z.object({
340
+ payment_code: z.string(),
341
+ expires_at: z.string().optional()
342
+ });
343
+ const QrAccountSchema = z.object({
344
+ qr_string: z.string()
345
+ });
346
+ const PayLaterAccountSchema = z.object({
347
+ account_id: z.string(),
348
+ account_holder_name: z.string().optional(),
349
+ currency: CurrencySchema.optional()
350
+ });
351
+ const SocialMediaAccountSchema = z.object({
352
+ account_id: z.string(),
353
+ account_handle: z.string().optional()
354
+ });
355
+ const PropertiesSchema = z.discriminatedUnion("type", [
356
+ z.object({
357
+ type: z.literal("BANK_ACCOUNT"),
358
+ properties: BankAccountSchema
359
+ }),
360
+ z.object({
361
+ type: z.literal("EWALLET"),
362
+ properties: EWalletAccountSchema
363
+ }),
364
+ z.object({
365
+ type: z.literal("CREDIT_CARD"),
366
+ properties: CreditCardAccountSchema
367
+ }),
368
+ z.object({
369
+ type: z.literal("OTC"),
370
+ properties: OTCAccountSchema
371
+ }),
372
+ z.object({
373
+ type: z.literal("QR_CODE"),
374
+ properties: QrAccountSchema
375
+ }),
376
+ z.object({
377
+ type: z.literal("PAY_LATER"),
378
+ properties: PayLaterAccountSchema
379
+ }),
380
+ z.object({
381
+ type: z.literal("SOCIAL_MEDIA"),
382
+ properties: SocialMediaAccountSchema
383
+ })
384
+ ]);
385
+ const IdentityAccountSchema = z.object({
386
+ type: AccountTypeSchema,
387
+ company: z.string().nullable().optional(),
388
+ description: z.string().nullable().optional(),
389
+ country: z.string().nullable().optional(),
390
+ properties: PropertiesSchema
391
+ });
392
+ const KYCDocumentSchema = z.object({
393
+ type: z.string(),
394
+ sub_type: z.string(),
395
+ country: z.string(),
396
+ document_name: z.string(),
397
+ document_number: z.string(),
398
+ expires_at: z.null(),
399
+ holder_name: z.string(),
400
+ document_images: z.array(z.string())
401
+ });
402
+ const CommonCustomerResourceSchema = z.object({
403
+ individual_detail: IndividualDetailSchema.optional(),
404
+ business_detail: BusinessDetailSchema.optional(),
405
+ email: z.string().email().optional(),
406
+ mobile_number: PhoneSchema.optional(),
407
+ phone_number: PhoneSchema.optional(),
408
+ hashed_phone_number: z.string().nullable().optional(),
409
+ addresses: z.array(AddressSchema).optional(),
410
+ identity_accounts: z.array(IdentityAccountSchema).optional(),
411
+ kyc_documents: z.array(KYCDocumentSchema).optional(),
412
+ description: z.string().nullable().optional(),
413
+ date_of_registration: z.string().nullable().optional(),
414
+ domicile_of_registration: z.string().nullable().optional(),
415
+ metadata: z.object({}).nullable().optional()
416
+ });
417
+ // Create a discriminated union for customer types
418
+ const CustomerSchema = z.discriminatedUnion("type", [
419
+ z.object({
420
+ type: z.literal("INDIVIDUAL"),
421
+ reference_id: z.string(),
422
+ individual_detail: IndividualDetailSchema,
423
+ business_detail: z.undefined().optional(),
424
+ email: z.string().email().optional(),
425
+ mobile_number: PhoneSchema.optional(),
426
+ phone_number: PhoneSchema.optional(),
427
+ hashed_phone_number: z.string().nullable().optional(),
428
+ addresses: z.array(AddressSchema).optional(),
429
+ identity_accounts: z.array(IdentityAccountSchema).optional(),
430
+ kyc_documents: z.array(KYCDocumentSchema).optional(),
431
+ description: z.string().nullable().optional(),
432
+ date_of_registration: z.string().nullable().optional(),
433
+ domicile_of_registration: z.string().nullable().optional(),
434
+ metadata: z.object({}).nullable().optional()
435
+ }),
436
+ z.object({
437
+ type: z.literal("BUSINESS"),
438
+ reference_id: z.string(),
439
+ individual_detail: z.undefined().optional(),
440
+ business_detail: BusinessDetailSchema,
441
+ email: z.string().email().optional(),
442
+ mobile_number: PhoneSchema.optional(),
443
+ phone_number: PhoneSchema.optional(),
444
+ hashed_phone_number: z.string().nullable().optional(),
445
+ addresses: z.array(AddressSchema).optional(),
446
+ identity_accounts: z.array(IdentityAccountSchema).optional(),
447
+ kyc_documents: z.array(KYCDocumentSchema).optional(),
448
+ description: z.string().nullable().optional(),
449
+ date_of_registration: z.string().nullable().optional(),
450
+ domicile_of_registration: z.string().nullable().optional(),
451
+ metadata: z.object({}).nullable().optional()
452
+ })
453
+ ]);
454
+ const GetCustomerSchema = z.object({
455
+ id: z.string()
456
+ });
457
+ // Create CustomerResourceSchema by extending both discriminated union options
458
+ const CustomerResourceSchema = z.discriminatedUnion("type", [
459
+ z.object({
460
+ type: z.literal("INDIVIDUAL"),
461
+ id: z.string(),
462
+ reference_id: z.string(),
463
+ individual_detail: IndividualDetailSchema,
464
+ business_detail: z.undefined().optional(),
465
+ email: z.string().email().optional(),
466
+ mobile_number: PhoneSchema.optional(),
467
+ phone_number: PhoneSchema.optional(),
468
+ hashed_phone_number: z.string().nullable().optional(),
469
+ addresses: z.array(AddressSchema).optional(),
470
+ identity_accounts: z.array(IdentityAccountSchema).optional(),
471
+ kyc_documents: z.array(KYCDocumentSchema).optional(),
472
+ description: z.string().nullable().optional(),
473
+ date_of_registration: z.string().nullable().optional(),
474
+ domicile_of_registration: z.string().nullable().optional(),
475
+ metadata: z.object({}).nullable().optional(),
476
+ created: z.string().datetime(),
477
+ updated: z.string().datetime()
478
+ }),
479
+ z.object({
480
+ type: z.literal("BUSINESS"),
481
+ id: z.string(),
482
+ reference_id: z.string(),
483
+ individual_detail: z.undefined().optional(),
484
+ business_detail: BusinessDetailSchema,
485
+ email: z.string().email().optional(),
486
+ mobile_number: PhoneSchema.optional(),
487
+ phone_number: PhoneSchema.optional(),
488
+ hashed_phone_number: z.string().nullable().optional(),
489
+ addresses: z.array(AddressSchema).optional(),
490
+ identity_accounts: z.array(IdentityAccountSchema).optional(),
491
+ kyc_documents: z.array(KYCDocumentSchema).optional(),
492
+ description: z.string().nullable().optional(),
493
+ date_of_registration: z.string().nullable().optional(),
494
+ domicile_of_registration: z.string().nullable().optional(),
495
+ metadata: z.object({}).nullable().optional(),
496
+ created: z.string().datetime(),
497
+ updated: z.string().datetime()
498
+ })
499
+ ]);
500
+ const GetCustomerByRefIdSchema = z.object({
501
+ reference_id: z.string()
502
+ });
503
+ z.object({
504
+ data: z.array(CustomerResourceSchema),
505
+ hasMore: z.boolean()
506
+ });
507
+ const UpdateParamsSchema = z.object({
508
+ id: z.string(),
509
+ payload: CommonCustomerResourceSchema
510
+ });
511
+
512
+ const createCustomer = async (params, axiosInstance, config)=>{
513
+ try {
514
+ const validatedParams = validateInput(CustomerSchema, params, "customer params");
515
+ const response = await axiosInstance.post(config?.url ?? "/customers", validatedParams, config);
516
+ // Note: Actual API response might not match discriminated union exactly
517
+ // For production use, consider making the schema more flexible
518
+ return response.data;
519
+ } catch (error) {
520
+ if (error instanceof Error && error.name === "AxiosError") {
521
+ handleAxiosError(error);
522
+ }
523
+ throw error;
524
+ }
525
+ };
526
+ const getCustomerId = async (params, axiosInstance, config)=>{
527
+ try {
528
+ const validatedParams = validateInput(GetCustomerSchema, params, "get customer params");
529
+ const response = await axiosInstance.get(config?.url ?? `/customers/${validatedParams.id}`, config);
530
+ // Note: Actual API response might not match discriminated union exactly
531
+ return response.data;
532
+ } catch (error) {
533
+ if (error instanceof Error && error.name === "AxiosError") {
534
+ handleAxiosError(error);
535
+ }
536
+ throw error;
537
+ }
538
+ };
539
+ const getCustomerRefId = async (params, axiosInstance, config)=>{
540
+ try {
541
+ const validatedParams = validateInput(GetCustomerByRefIdSchema, params, "get customer by ref params");
542
+ const response = await axiosInstance.get(config?.url ?? `/customers?reference_id=${validatedParams.reference_id}`, config);
543
+ // Note: Actual API response might not match discriminated union exactly
544
+ return response.data;
545
+ } catch (error) {
546
+ if (error instanceof Error && error.name === "AxiosError") {
547
+ handleAxiosError(error);
548
+ }
549
+ throw error;
550
+ }
551
+ };
552
+ const updateCustomer = async (params, axiosInstance, config)=>{
553
+ try {
554
+ const validatedParams = validateInput(UpdateParamsSchema, params, "update customer params");
555
+ const response = await axiosInstance.patch(config?.url ?? `/customers/${validatedParams.id}`, validatedParams.payload, config);
556
+ // Note: Actual API response might not match discriminated union exactly
557
+ return response.data;
558
+ } catch (error) {
559
+ if (error instanceof Error && error.name === "AxiosError") {
560
+ handleAxiosError(error);
561
+ }
562
+ throw error;
563
+ }
564
+ };
565
+
566
+ const createEwalletCharge = async (params, axiosInstance, config)=>(await axiosInstance.post(config?.url ?? "/ewallets/charges", params, config)).data;
567
+ const getEwalletCharge = async (params, axiosInstance, config)=>(await axiosInstance.get(config?.url ?? `/ewallets/charges/${params.id}`, config)).data;
568
+
569
+ // Payment Method Types
570
+ const PaymentMethodTypeSchema = z.union([
571
+ z.literal("CARD"),
572
+ z.literal("BANK_ACCOUNT"),
573
+ z.literal("EWALLET"),
574
+ z.literal("OVER_THE_COUNTER"),
575
+ z.literal("VIRTUAL_ACCOUNT"),
576
+ z.literal("QR_CODE")
577
+ ]);
578
+ // Payment Method Status
579
+ const PaymentMethodStatusSchema = z.union([
580
+ z.literal("ACTIVE"),
581
+ z.literal("INACTIVE"),
582
+ z.literal("PENDING"),
583
+ z.literal("EXPIRED"),
584
+ z.literal("FAILED")
585
+ ]);
586
+ // Card Properties
587
+ const CardPropertiesSchema = z.object({
588
+ card_last_four: z.string(),
589
+ card_expiry_month: z.string(),
590
+ card_expiry_year: z.string(),
591
+ network: z.string(),
592
+ country: CountrySchema.optional(),
593
+ issuer: z.string().optional(),
594
+ type: z.union([
595
+ z.literal("CREDIT"),
596
+ z.literal("DEBIT")
597
+ ]).optional(),
598
+ currency: CurrencySchema.optional()
599
+ });
600
+ // Bank Account Properties
601
+ const BankAccountPropertiesSchema = z.object({
602
+ account_number: z.string(),
603
+ account_holder_name: z.string(),
604
+ bank_code: z.string(),
605
+ account_type: z.string().optional(),
606
+ currency: CurrencySchema
607
+ });
608
+ // E-wallet Properties
609
+ const EwalletPropertiesSchema = z.object({
610
+ account_details: z.string(),
611
+ currency: CurrencySchema
612
+ });
613
+ // Payment Method Properties (Discriminated Union)
614
+ z.discriminatedUnion("type", [
615
+ z.object({
616
+ type: z.literal("CARD"),
617
+ card: CardPropertiesSchema
618
+ }),
619
+ z.object({
620
+ type: z.literal("BANK_ACCOUNT"),
621
+ bank_account: BankAccountPropertiesSchema
622
+ }),
623
+ z.object({
624
+ type: z.literal("EWALLET"),
625
+ ewallet: EwalletPropertiesSchema
626
+ })
627
+ ]);
628
+ // Create Payment Method Request
629
+ const CreatePaymentMethodSchema = z.object({
630
+ type: PaymentMethodTypeSchema,
631
+ country: CountrySchema.optional(),
632
+ reusability: z.union([
633
+ z.literal("ONE_TIME_USE"),
634
+ z.literal("MULTIPLE_USE")
635
+ ]),
636
+ description: z.string().optional(),
637
+ reference_id: z.string().optional(),
638
+ metadata: z.record(z.unknown()).optional(),
639
+ // Specific properties based on type
640
+ card: z.object({
641
+ currency: CurrencySchema.optional(),
642
+ channel_properties: z.object({
643
+ success_return_url: z.string().url().optional(),
644
+ failure_return_url: z.string().url().optional()
645
+ }).optional()
646
+ }).optional(),
647
+ bank_account: z.object({
648
+ currency: CurrencySchema,
649
+ channel_properties: z.object({
650
+ account_mobile_number: z.string().optional(),
651
+ card_last_four: z.string().optional(),
652
+ card_expiry_month: z.string().optional(),
653
+ card_expiry_year: z.string().optional(),
654
+ account_email: z.string().email().optional()
655
+ }).optional()
656
+ }).optional(),
657
+ ewallet: z.object({
658
+ channel_code: z.string(),
659
+ channel_properties: z.object({
660
+ success_return_url: z.string().url().optional(),
661
+ failure_return_url: z.string().url().optional(),
662
+ cancel_return_url: z.string().url().optional()
663
+ }).optional()
664
+ }).optional()
665
+ });
666
+ // Update Payment Method Request
667
+ const UpdatePaymentMethodSchema = z.object({
668
+ description: z.string().optional(),
669
+ reference_id: z.string().optional(),
670
+ status: PaymentMethodStatusSchema.optional(),
671
+ metadata: z.record(z.unknown()).optional()
672
+ });
673
+ // Payment Method Resource
674
+ const PaymentMethodResourceSchema = z.object({
675
+ id: z.string(),
676
+ type: PaymentMethodTypeSchema,
677
+ country: CountrySchema.optional(),
678
+ business_id: z.string(),
679
+ customer_id: z.string().optional(),
680
+ reference_id: z.string().optional(),
681
+ description: z.string().optional(),
682
+ status: PaymentMethodStatusSchema,
683
+ reusability: z.union([
684
+ z.literal("ONE_TIME_USE"),
685
+ z.literal("MULTIPLE_USE")
686
+ ]),
687
+ actions: z.array(z.object({
688
+ action: z.string(),
689
+ url: z.string().url().optional(),
690
+ url_type: z.string().optional(),
691
+ method: z.string().optional()
692
+ })).optional(),
693
+ metadata: z.record(z.unknown()).optional(),
694
+ billing_information: z.object({
695
+ country: CountrySchema.optional(),
696
+ street_line1: z.string().optional(),
697
+ street_line2: z.string().optional(),
698
+ city: z.string().optional(),
699
+ province_state: z.string().optional(),
700
+ postal_code: z.string().optional()
701
+ }).optional(),
702
+ failure_code: z.string().nullable().optional(),
703
+ created: z.string().datetime(),
704
+ updated: z.string().datetime(),
705
+ // Type-specific properties
706
+ card: CardPropertiesSchema.optional(),
707
+ bank_account: BankAccountPropertiesSchema.optional(),
708
+ ewallet: EwalletPropertiesSchema.optional()
709
+ });
710
+ // Get Payment Method Request
711
+ const GetPaymentMethodSchema = z.object({
712
+ id: z.string()
713
+ });
714
+ // List Payment Methods Request
715
+ const ListPaymentMethodsSchema = z.object({
716
+ id: z.array(z.string()).optional(),
717
+ type: z.array(PaymentMethodTypeSchema).optional(),
718
+ status: z.array(PaymentMethodStatusSchema).optional(),
719
+ reusability: z.union([
720
+ z.literal("ONE_TIME_USE"),
721
+ z.literal("MULTIPLE_USE")
722
+ ]).optional(),
723
+ customer_id: z.string().optional(),
724
+ reference_id: z.string().optional(),
725
+ after_id: z.string().optional(),
726
+ before_id: z.string().optional(),
727
+ limit: z.number().min(1).max(100).default(10).optional()
728
+ });
729
+ // List Payment Methods Response
730
+ z.object({
731
+ data: z.array(PaymentMethodResourceSchema),
732
+ has_more: z.boolean(),
733
+ links: z.object({
734
+ href: z.string(),
735
+ rel: z.string(),
736
+ method: z.string()
737
+ }).array()
738
+ });
739
+ // Update Payment Method Params
740
+ const UpdatePaymentMethodParamsSchema = z.object({
741
+ id: z.string(),
742
+ payload: UpdatePaymentMethodSchema
743
+ });
744
+
745
+ const createPaymentMethod = async (params, axiosInstance, config)=>{
746
+ try {
747
+ const validatedParams = validateInput(CreatePaymentMethodSchema, params, "payment method params");
748
+ const response = await axiosInstance.post(config?.url ?? "/v2/payment_methods", validatedParams, config);
749
+ // Note: Actual API response handling - relaxed validation for production flexibility
750
+ return response.data;
751
+ } catch (error) {
752
+ if (error instanceof Error && error.name === "AxiosError") {
753
+ handleAxiosError(error);
754
+ }
755
+ throw error;
756
+ }
757
+ };
758
+ const getPaymentMethod = async (params, axiosInstance, config)=>{
759
+ try {
760
+ const validatedParams = validateInput(GetPaymentMethodSchema, params, "get payment method params");
761
+ const response = await axiosInstance.get(config?.url ?? `/v2/payment_methods/${validatedParams.id}`, config);
762
+ return response.data;
763
+ } catch (error) {
764
+ if (error instanceof Error && error.name === "AxiosError") {
765
+ handleAxiosError(error);
766
+ }
767
+ throw error;
768
+ }
769
+ };
770
+ const listPaymentMethods = async (params, axiosInstance, config)=>{
771
+ try {
772
+ const validatedParams = params ? validateInput(ListPaymentMethodsSchema, params, "list payment methods params") : {};
773
+ const queryParams = new URLSearchParams();
774
+ if (validatedParams.id) {
775
+ validatedParams.id.forEach((id)=>queryParams.append("id[]", id));
776
+ }
777
+ if (validatedParams.type) {
778
+ validatedParams.type.forEach((type)=>queryParams.append("type[]", type));
779
+ }
780
+ if (validatedParams.status) {
781
+ validatedParams.status.forEach((status)=>queryParams.append("status[]", status));
782
+ }
783
+ if (validatedParams.reusability) {
784
+ queryParams.append("reusability", validatedParams.reusability);
785
+ }
786
+ if (validatedParams.customer_id) {
787
+ queryParams.append("customer_id", validatedParams.customer_id);
788
+ }
789
+ if (validatedParams.reference_id) {
790
+ queryParams.append("reference_id", validatedParams.reference_id);
791
+ }
792
+ if (validatedParams.after_id) {
793
+ queryParams.append("after_id", validatedParams.after_id);
794
+ }
795
+ if (validatedParams.before_id) {
796
+ queryParams.append("before_id", validatedParams.before_id);
797
+ }
798
+ if (validatedParams.limit) {
799
+ queryParams.append("limit", validatedParams.limit.toString());
800
+ }
801
+ const queryString = queryParams.toString();
802
+ const url = queryString ? `/v2/payment_methods?${queryString}` : "/v2/payment_methods";
803
+ const response = await axiosInstance.get(config?.url ?? url, config);
804
+ return response.data;
805
+ } catch (error) {
806
+ if (error instanceof Error && error.name === "AxiosError") {
807
+ handleAxiosError(error);
808
+ }
809
+ throw error;
810
+ }
811
+ };
812
+ const updatePaymentMethod = async (params, axiosInstance, config)=>{
813
+ try {
814
+ const validatedParams = validateInput(UpdatePaymentMethodParamsSchema, params, "update payment method params");
815
+ const response = await axiosInstance.patch(config?.url ?? `/v2/payment_methods/${validatedParams.id}`, validatedParams.payload, config);
816
+ return response.data;
817
+ } catch (error) {
818
+ if (error instanceof Error && error.name === "AxiosError") {
819
+ handleAxiosError(error);
820
+ }
821
+ throw error;
822
+ }
823
+ };
824
+
825
+ // Invoice Status
826
+ const InvoiceStatusSchema = z.union([
827
+ z.literal("PENDING"),
828
+ z.literal("PAID"),
829
+ z.literal("SETTLED"),
830
+ z.literal("EXPIRED")
831
+ ]);
832
+ // Payer Email
833
+ z.object({
834
+ email: z.string().email()
835
+ });
836
+ // Invoice Item
837
+ const InvoiceItemSchema = z.object({
838
+ name: z.string(),
839
+ quantity: z.number().positive(),
840
+ price: z.number().positive(),
841
+ category: z.string().optional(),
842
+ url: z.string().url().optional()
843
+ });
844
+ // Customer Notification Preference
845
+ const CustomerNotificationPreferenceSchema = z.object({
846
+ invoice_created: z.array(z.union([
847
+ z.literal("whatsapp"),
848
+ z.literal("sms"),
849
+ z.literal("email")
850
+ ])).optional(),
851
+ invoice_reminder: z.array(z.union([
852
+ z.literal("whatsapp"),
853
+ z.literal("sms"),
854
+ z.literal("email")
855
+ ])).optional(),
856
+ invoice_paid: z.array(z.union([
857
+ z.literal("whatsapp"),
858
+ z.literal("sms"),
859
+ z.literal("email")
860
+ ])).optional(),
861
+ invoice_expired: z.array(z.union([
862
+ z.literal("whatsapp"),
863
+ z.literal("sms"),
864
+ z.literal("email")
865
+ ])).optional()
866
+ });
867
+ // Customer Details
868
+ const CustomerDetailsSchema = z.object({
869
+ customer_name: z.string().optional(),
870
+ customer_email: z.string().email().optional(),
871
+ customer_phone: z.string().optional(),
872
+ billing_address: z.object({
873
+ first_name: z.string().optional(),
874
+ last_name: z.string().optional(),
875
+ address: z.string().optional(),
876
+ city: z.string().optional(),
877
+ postal_code: z.string().optional(),
878
+ phone: z.string().optional(),
879
+ country_code: CountrySchema.optional()
880
+ }).optional(),
881
+ shipping_address: z.object({
882
+ first_name: z.string().optional(),
883
+ last_name: z.string().optional(),
884
+ address: z.string().optional(),
885
+ city: z.string().optional(),
886
+ postal_code: z.string().optional(),
887
+ phone: z.string().optional(),
888
+ country_code: CountrySchema.optional()
889
+ }).optional()
890
+ });
891
+ // Fee Details
892
+ const FeeSchema = z.object({
893
+ type: z.string(),
894
+ value: z.number()
895
+ });
896
+ // Available Bank and E-wallet
897
+ const AvailableBankSchema = z.object({
898
+ bank_code: z.string(),
899
+ collection_type: z.string(),
900
+ bank_branch: z.string(),
901
+ transfer_amount: z.number(),
902
+ bank_account_number: z.string(),
903
+ account_holder_name: z.string(),
904
+ identity_amount: z.number().optional()
905
+ });
906
+ const AvailableEwalletSchema = z.object({
907
+ ewallet_type: z.string()
908
+ });
909
+ const AvailableRetailOutletSchema = z.object({
910
+ retail_outlet_name: z.string()
911
+ });
912
+ // Create Invoice Schema
913
+ const CreateInvoiceSchema = z.object({
914
+ external_id: z.string(),
915
+ payer_email: z.string().email(),
916
+ description: z.string(),
917
+ amount: z.number().positive(),
918
+ invoice_duration: z.number().positive().optional(),
919
+ callback_virtual_account_id: z.string().optional(),
920
+ should_exclude_credit_card: z.boolean().optional(),
921
+ should_send_email: z.boolean().optional(),
922
+ customer_name: z.string().optional(),
923
+ customer_email: z.string().email().optional(),
924
+ customer_phone: z.string().optional(),
925
+ customer: CustomerDetailsSchema.optional(),
926
+ customer_notification_preference: CustomerNotificationPreferenceSchema.optional(),
927
+ success_redirect_url: z.string().url().optional(),
928
+ failure_redirect_url: z.string().url().optional(),
929
+ payment_methods: z.array(z.string()).optional(),
930
+ mid_label: z.string().optional(),
931
+ should_authenticate_credit_card: z.boolean().optional(),
932
+ currency: CurrencySchema.optional(),
933
+ items: z.array(InvoiceItemSchema).optional(),
934
+ fixed_va: z.boolean().optional(),
935
+ reminder_time_unit: z.union([
936
+ z.literal("days"),
937
+ z.literal("hours"),
938
+ z.literal("minutes")
939
+ ]).optional(),
940
+ reminder_time: z.number().optional(),
941
+ locale: z.string().optional(),
942
+ fees: z.array(FeeSchema).optional(),
943
+ metadata: z.record(z.unknown()).optional()
944
+ });
945
+ // Update Invoice Schema
946
+ const UpdateInvoiceSchema = z.object({
947
+ should_send_email: z.boolean().optional(),
948
+ customer_name: z.string().optional(),
949
+ customer_email: z.string().email().optional(),
950
+ customer_phone: z.string().optional(),
951
+ customer: CustomerDetailsSchema.optional(),
952
+ customer_notification_preference: CustomerNotificationPreferenceSchema.optional(),
953
+ success_redirect_url: z.string().url().optional(),
954
+ failure_redirect_url: z.string().url().optional(),
955
+ items: z.array(InvoiceItemSchema).optional(),
956
+ metadata: z.record(z.unknown()).optional()
957
+ });
958
+ // Invoice Resource
959
+ const InvoiceResourceSchema = z.object({
960
+ id: z.string(),
961
+ external_id: z.string(),
962
+ user_id: z.string(),
963
+ status: InvoiceStatusSchema,
964
+ merchant_name: z.string(),
965
+ merchant_profile_picture_url: z.string().url(),
966
+ amount: z.number(),
967
+ payer_email: z.string().email(),
968
+ description: z.string(),
969
+ expiry_date: z.string().datetime(),
970
+ invoice_url: z.string().url(),
971
+ should_exclude_credit_card: z.boolean(),
972
+ should_send_email: z.boolean(),
973
+ created: z.string().datetime(),
974
+ updated: z.string().datetime(),
975
+ currency: CurrencySchema,
976
+ paid_amount: z.number().optional(),
977
+ credit_card_charge_id: z.string().optional(),
978
+ payment_method: z.string().optional(),
979
+ payment_channel: z.string().optional(),
980
+ payment_destination: z.string().optional(),
981
+ payment_id: z.string().optional(),
982
+ paid_at: z.string().datetime().optional(),
983
+ bank_code: z.string().optional(),
984
+ ewallet_type: z.string().optional(),
985
+ on_demand_link: z.string().url().optional(),
986
+ recurring_payment_id: z.string().optional(),
987
+ // Customer information
988
+ customer_name: z.string().optional(),
989
+ customer_email: z.string().email().optional(),
990
+ customer_phone: z.string().optional(),
991
+ customer: CustomerDetailsSchema.optional(),
992
+ customer_notification_preference: CustomerNotificationPreferenceSchema.optional(),
993
+ // URLs
994
+ success_redirect_url: z.string().url().optional(),
995
+ failure_redirect_url: z.string().url().optional(),
996
+ // Items and fees
997
+ items: z.array(InvoiceItemSchema).optional(),
998
+ fees: z.array(FeeSchema).optional(),
999
+ // Available payment methods
1000
+ available_banks: z.array(AvailableBankSchema).optional(),
1001
+ available_ewallets: z.array(AvailableEwalletSchema).optional(),
1002
+ available_retail_outlets: z.array(AvailableRetailOutletSchema).optional(),
1003
+ available_paylaters: z.array(z.object({
1004
+ paylater_type: z.string()
1005
+ })).optional(),
1006
+ available_qr_codes: z.array(z.object({
1007
+ qr_code_type: z.string()
1008
+ })).optional(),
1009
+ available_direct_debits: z.array(z.object({
1010
+ direct_debit_type: z.string()
1011
+ })).optional(),
1012
+ should_authenticate_credit_card: z.boolean().optional(),
1013
+ metadata: z.record(z.unknown()).optional()
1014
+ });
1015
+ // Get Invoice Schema
1016
+ const GetInvoiceSchema = z.object({
1017
+ invoice_id: z.string()
1018
+ });
1019
+ // List Invoices Schema
1020
+ const ListInvoicesSchema = z.object({
1021
+ statuses: z.array(InvoiceStatusSchema).optional(),
1022
+ limit: z.number().min(1).max(100).optional(),
1023
+ created_after: z.string().datetime().optional(),
1024
+ created_before: z.string().datetime().optional(),
1025
+ paid_after: z.string().datetime().optional(),
1026
+ paid_before: z.string().datetime().optional(),
1027
+ expired_after: z.string().datetime().optional(),
1028
+ expired_before: z.string().datetime().optional(),
1029
+ last_invoice: z.string().optional(),
1030
+ client_types: z.array(z.string()).optional(),
1031
+ payment_channels: z.array(z.string()).optional(),
1032
+ on_demand_link: z.string().optional(),
1033
+ recurring_payment_id: z.string().optional()
1034
+ });
1035
+ // List Invoices Response
1036
+ z.object({
1037
+ has_more: z.boolean(),
1038
+ data: z.array(InvoiceResourceSchema)
1039
+ });
1040
+ // Update Invoice Params
1041
+ const UpdateInvoiceParamsSchema = z.object({
1042
+ invoice_id: z.string(),
1043
+ payload: UpdateInvoiceSchema
1044
+ });
1045
+ // Expire Invoice Schema
1046
+ const ExpireInvoiceSchema = z.object({
1047
+ invoice_id: z.string()
1048
+ });
1049
+
1050
+ const createInvoice = async (params, axiosInstance, config)=>{
1051
+ try {
1052
+ const validatedParams = validateInput(CreateInvoiceSchema, params, "invoice params");
1053
+ const response = await axiosInstance.post(config?.url ?? "/v2/invoices", validatedParams, config);
1054
+ return response.data;
1055
+ } catch (error) {
1056
+ if (error instanceof Error && error.name === "AxiosError") {
1057
+ handleAxiosError(error);
1058
+ }
1059
+ throw error;
1060
+ }
1061
+ };
1062
+ const getInvoice = async (params, axiosInstance, config)=>{
1063
+ try {
1064
+ const validatedParams = validateInput(GetInvoiceSchema, params, "get invoice params");
1065
+ const response = await axiosInstance.get(config?.url ?? `/v2/invoices/${validatedParams.invoice_id}`, config);
1066
+ return response.data;
1067
+ } catch (error) {
1068
+ if (error instanceof Error && error.name === "AxiosError") {
1069
+ handleAxiosError(error);
1070
+ }
1071
+ throw error;
1072
+ }
1073
+ };
1074
+ const listInvoices = async (params, axiosInstance, config)=>{
1075
+ try {
1076
+ const validatedParams = params ? validateInput(ListInvoicesSchema, params, "list invoices params") : {};
1077
+ const queryParams = new URLSearchParams();
1078
+ if (validatedParams.statuses) {
1079
+ validatedParams.statuses.forEach((status)=>queryParams.append("statuses[]", status));
1080
+ }
1081
+ if (validatedParams.limit) {
1082
+ queryParams.append("limit", validatedParams.limit.toString());
1083
+ }
1084
+ if (validatedParams.created_after) {
1085
+ queryParams.append("created_after", validatedParams.created_after);
1086
+ }
1087
+ if (validatedParams.created_before) {
1088
+ queryParams.append("created_before", validatedParams.created_before);
1089
+ }
1090
+ if (validatedParams.paid_after) {
1091
+ queryParams.append("paid_after", validatedParams.paid_after);
1092
+ }
1093
+ if (validatedParams.paid_before) {
1094
+ queryParams.append("paid_before", validatedParams.paid_before);
1095
+ }
1096
+ if (validatedParams.expired_after) {
1097
+ queryParams.append("expired_after", validatedParams.expired_after);
1098
+ }
1099
+ if (validatedParams.expired_before) {
1100
+ queryParams.append("expired_before", validatedParams.expired_before);
1101
+ }
1102
+ if (validatedParams.last_invoice) {
1103
+ queryParams.append("last_invoice", validatedParams.last_invoice);
1104
+ }
1105
+ if (validatedParams.client_types) {
1106
+ validatedParams.client_types.forEach((type)=>queryParams.append("client_types[]", type));
1107
+ }
1108
+ if (validatedParams.payment_channels) {
1109
+ validatedParams.payment_channels.forEach((channel)=>queryParams.append("payment_channels[]", channel));
1110
+ }
1111
+ if (validatedParams.on_demand_link) {
1112
+ queryParams.append("on_demand_link", validatedParams.on_demand_link);
1113
+ }
1114
+ if (validatedParams.recurring_payment_id) {
1115
+ queryParams.append("recurring_payment_id", validatedParams.recurring_payment_id);
1116
+ }
1117
+ const queryString = queryParams.toString();
1118
+ const url = queryString ? `/v2/invoices?${queryString}` : "/v2/invoices";
1119
+ const response = await axiosInstance.get(config?.url ?? url, config);
1120
+ return response.data;
1121
+ } catch (error) {
1122
+ if (error instanceof Error && error.name === "AxiosError") {
1123
+ handleAxiosError(error);
1124
+ }
1125
+ throw error;
1126
+ }
1127
+ };
1128
+ const updateInvoice = async (params, axiosInstance, config)=>{
1129
+ try {
1130
+ const validatedParams = validateInput(UpdateInvoiceParamsSchema, params, "update invoice params");
1131
+ const response = await axiosInstance.patch(config?.url ?? `/v2/invoices/${validatedParams.invoice_id}`, validatedParams.payload, config);
1132
+ return response.data;
1133
+ } catch (error) {
1134
+ if (error instanceof Error && error.name === "AxiosError") {
1135
+ handleAxiosError(error);
1136
+ }
1137
+ throw error;
1138
+ }
1139
+ };
1140
+ const expireInvoice = async (params, axiosInstance, config)=>{
1141
+ try {
1142
+ const validatedParams = validateInput(ExpireInvoiceSchema, params, "expire invoice params");
1143
+ const response = await axiosInstance.post(config?.url ?? `/invoices/${validatedParams.invoice_id}/expire`, {}, config);
1144
+ return response.data;
1145
+ } catch (error) {
1146
+ if (error instanceof Error && error.name === "AxiosError") {
1147
+ handleAxiosError(error);
1148
+ }
1149
+ throw error;
1150
+ }
1151
+ };
1152
+
1153
+ const btoa = (string)=>{
1154
+ if (typeof window === "undefined") {
1155
+ return Buffer.from(string).toString("base64");
1156
+ }
1157
+ return window.btoa(string);
1158
+ };
1159
+ const createFn = (fn, axiosInstance)=>{
1160
+ return (data)=>fn(data, axiosInstance);
1161
+ };
1162
+ const Xendit = (key, options = {})=>{
1163
+ const axiosInstance = createAxiosInstance({
1164
+ headers: {
1165
+ Authorization: `Basic ${btoa(key + ":")}`
1166
+ }
1167
+ });
1168
+ // Apply rate limiting if configured
1169
+ if (options.rateLimit) {
1170
+ setupRateLimit(axiosInstance, options.rateLimit);
1171
+ }
1172
+ if (key.includes("development")) {
1173
+ console.log("👾 You are on → TEST MODE");
1174
+ }
1175
+ return {
1176
+ customer: {
1177
+ create: createFn(createCustomer, axiosInstance),
1178
+ getById: createFn(getCustomerId, axiosInstance),
1179
+ getByRefId: createFn(getCustomerRefId, axiosInstance),
1180
+ update: createFn(updateCustomer, axiosInstance)
1181
+ },
1182
+ ewallet: {
1183
+ charge: createFn(createEwalletCharge, axiosInstance),
1184
+ get: createFn(getEwalletCharge, axiosInstance)
1185
+ },
1186
+ paymentMethod: {
1187
+ create: createFn(createPaymentMethod, axiosInstance),
1188
+ get: createFn(getPaymentMethod, axiosInstance),
1189
+ list: (params)=>listPaymentMethods(params, axiosInstance),
1190
+ update: createFn(updatePaymentMethod, axiosInstance)
1191
+ },
1192
+ invoice: {
1193
+ create: createFn(createInvoice, axiosInstance),
1194
+ get: createFn(getInvoice, axiosInstance),
1195
+ list: (params)=>listInvoices(params, axiosInstance),
1196
+ update: createFn(updateInvoice, axiosInstance),
1197
+ expire: createFn(expireInvoice, axiosInstance)
1198
+ }
1199
+ };
1200
+ };
1201
+
1202
+ // Type guard for phone numbers
1203
+ function isValidPhone(value) {
1204
+ return typeof value === "string" && value.startsWith("+") && value.length >= 8 && value.length <= 15;
1205
+ }
1206
+ // Type guard for country codes
1207
+ function isValidCountry(value) {
1208
+ return [
1209
+ "PH",
1210
+ "ID",
1211
+ "MY",
1212
+ "TH",
1213
+ "VN"
1214
+ ].includes(value);
1215
+ }
1216
+ // Type guard for currency codes
1217
+ function isValidCurrency(value) {
1218
+ return [
1219
+ "PHP",
1220
+ "IDR",
1221
+ "MYR",
1222
+ "THB",
1223
+ "VND"
1224
+ ].includes(value);
1225
+ }
1226
+ // Type guard for customer type
1227
+ function isValidCustomerType(value) {
1228
+ return [
1229
+ "INDIVIDUAL",
1230
+ "BUSINESS"
1231
+ ].includes(value);
1232
+ }
1233
+ // Type guard for checkout method
1234
+ function isValidCheckoutMethod(value) {
1235
+ return [
1236
+ "ONE_TIME_PAYMENT",
1237
+ "TOKENIZED_PAYMENT"
1238
+ ].includes(value);
1239
+ }
1240
+ // Generic type guard for checking if value is not null or undefined
1241
+ function isNotNullOrUndefined(value) {
1242
+ return value !== null && value !== undefined;
1243
+ }
1244
+ // Type guard for checking if value is a valid URL
1245
+ function isValidUrl(value) {
1246
+ try {
1247
+ new URL(value);
1248
+ return true;
1249
+ } catch {
1250
+ return false;
1251
+ }
1252
+ }
1253
+ // Type guard for checking if value is a valid email
1254
+ function isValidEmail(value) {
1255
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1256
+ return emailRegex.test(value);
1257
+ }
1258
+ // Type guard for checking if value is a valid date string
1259
+ function isValidDateString(value) {
1260
+ const date = new Date(value);
1261
+ return !isNaN(date.getTime()) && value === date.toISOString();
1262
+ }
1263
+
1264
+ // Webhook Event Types
1265
+ const WebhookEventTypeSchema = z.union([
1266
+ z.literal("invoice.paid"),
1267
+ z.literal("invoice.expired"),
1268
+ z.literal("payment.succeeded"),
1269
+ z.literal("payment.failed"),
1270
+ z.literal("ewallet.charge.succeeded"),
1271
+ z.literal("ewallet.charge.pending"),
1272
+ z.literal("ewallet.charge.failed"),
1273
+ z.literal("payment_method.activate"),
1274
+ z.literal("payment_method.expire"),
1275
+ z.literal("customer.created"),
1276
+ z.literal("customer.updated")
1277
+ ]);
1278
+ // Base Webhook Event Schema
1279
+ const WebhookEventSchema = z.object({
1280
+ id: z.string(),
1281
+ event: WebhookEventTypeSchema,
1282
+ api_version: z.string(),
1283
+ created: z.string().datetime(),
1284
+ business_id: z.string(),
1285
+ data: z.record(z.unknown())
1286
+ });
1287
+ /**
1288
+ * Verify webhook signature from Xendit
1289
+ * @param options Verification options
1290
+ * @returns true if signature is valid, false otherwise
1291
+ */ function verifyWebhookSignature(options) {
1292
+ const { callbackToken, receivedToken } = options;
1293
+ // Simple token comparison for Xendit webhooks
1294
+ return callbackToken === receivedToken;
1295
+ }
1296
+ /**
1297
+ * Advanced webhook signature verification using HMAC
1298
+ * @param options Verification options with HMAC
1299
+ * @returns true if signature is valid, false otherwise
1300
+ */ function verifyWebhookHmac(options) {
1301
+ const { secret, requestBody, signature } = options;
1302
+ const body = typeof requestBody === "string" ? requestBody : requestBody.toString("utf8");
1303
+ const expectedSignature = createHmac("sha256", secret).update(body).digest("hex");
1304
+ return `sha256=${expectedSignature}` === signature;
1305
+ }
1306
+ /**
1307
+ * Parse and validate webhook event
1308
+ * @param rawEvent Raw webhook event data
1309
+ * @returns Parsed and validated webhook event
1310
+ */ function parseWebhookEvent(rawEvent) {
1311
+ const result = WebhookEventSchema.safeParse(rawEvent);
1312
+ if (!result.success) {
1313
+ throw new Error(`Invalid webhook event format: ${result.error.message}`);
1314
+ }
1315
+ return result.data;
1316
+ }
1317
+ /**
1318
+ * Handle webhook events with type-safe handlers
1319
+ * @param event Webhook event
1320
+ * @param handlers Event handlers
1321
+ */ async function handleWebhookEvent(event, handlers) {
1322
+ const handler = handlers[event.event];
1323
+ if (handler) {
1324
+ await handler(event);
1325
+ } else {
1326
+ console.warn(`No handler found for webhook event: ${event.event}`);
1327
+ }
1328
+ }
1329
+ /**
1330
+ * Create a webhook processor with built-in verification
1331
+ */ function createWebhookProcessor(options) {
1332
+ return {
1333
+ /**
1334
+ * Process a webhook request
1335
+ */ async processWebhook (requestBody, headers, handlers) {
1336
+ try {
1337
+ // Verify signature
1338
+ if (options.callbackToken) {
1339
+ const receivedToken = headers["x-callback-token"] || headers["X-Callback-Token"];
1340
+ if (!receivedToken || !verifyWebhookSignature({
1341
+ callbackToken: options.callbackToken,
1342
+ requestBody,
1343
+ receivedToken
1344
+ })) {
1345
+ return {
1346
+ success: false,
1347
+ error: "Invalid webhook signature"
1348
+ };
1349
+ }
1350
+ }
1351
+ if (options.hmacSecret) {
1352
+ const signature = headers["x-xendit-signature"] || headers["X-Xendit-Signature"];
1353
+ if (!signature || !verifyWebhookHmac({
1354
+ secret: options.hmacSecret,
1355
+ requestBody,
1356
+ signature
1357
+ })) {
1358
+ return {
1359
+ success: false,
1360
+ error: "Invalid HMAC signature"
1361
+ };
1362
+ }
1363
+ }
1364
+ // Parse event
1365
+ const body = typeof requestBody === "string" ? requestBody : requestBody.toString("utf8");
1366
+ const rawEvent = JSON.parse(body);
1367
+ const event = parseWebhookEvent(rawEvent);
1368
+ // Handle event
1369
+ await handleWebhookEvent(event, handlers);
1370
+ return {
1371
+ success: true
1372
+ };
1373
+ } catch (error) {
1374
+ return {
1375
+ success: false,
1376
+ error: error instanceof Error ? error.message : "Unknown error"
1377
+ };
1378
+ }
1379
+ }
1380
+ };
1381
+ }
1382
+
1383
+ // Generic pagination response schema
1384
+ const PaginationMetaSchema = z.object({
1385
+ has_more: z.boolean(),
1386
+ after_id: z.string().optional(),
1387
+ before_id: z.string().optional(),
1388
+ total_count: z.number().optional()
1389
+ });
1390
+ const PaginatedResponseSchema = (itemSchema)=>z.object({
1391
+ data: z.array(itemSchema),
1392
+ has_more: z.boolean(),
1393
+ after_id: z.string().optional(),
1394
+ before_id: z.string().optional(),
1395
+ total_count: z.number().optional()
1396
+ });
1397
+ /**
1398
+ * Helper to build pagination query parameters
1399
+ */ function buildPaginationParams(options) {
1400
+ const params = {};
1401
+ if (options.limit) {
1402
+ params.limit = options.limit.toString();
1403
+ }
1404
+ if (options.after_id) {
1405
+ params.after_id = options.after_id;
1406
+ }
1407
+ if (options.before_id) {
1408
+ params.before_id = options.before_id;
1409
+ }
1410
+ return params;
1411
+ }
1412
+ /**
1413
+ * Generic paginated API fetcher
1414
+ */ async function fetchPaginated(axiosInstance, endpoint, itemSchema, options = {}) {
1415
+ const params = buildPaginationParams(options);
1416
+ const response = await axiosInstance.get(endpoint, {
1417
+ params
1418
+ });
1419
+ const paginatedSchema = PaginatedResponseSchema(itemSchema);
1420
+ const result = paginatedSchema.parse(response.data);
1421
+ return result;
1422
+ }
1423
+ /**
1424
+ * Auto-paginate through all pages and return all items
1425
+ */ async function fetchAllPages(axiosInstance, endpoint, itemSchema, options = {}) {
1426
+ const { limit = 10, maxPages = 100, maxItems = Infinity, ...paginationOptions } = options;
1427
+ let allItems = [];
1428
+ let currentAfter = paginationOptions.after_id;
1429
+ let pageCount = 0;
1430
+ while(pageCount < maxPages && allItems.length < maxItems){
1431
+ const response = await fetchPaginated(axiosInstance, endpoint, itemSchema, {
1432
+ ...paginationOptions,
1433
+ limit,
1434
+ after_id: currentAfter
1435
+ });
1436
+ allItems = allItems.concat(response.data);
1437
+ pageCount++;
1438
+ // Stop if we've reached the maxItems limit
1439
+ if (allItems.length >= maxItems) {
1440
+ allItems = allItems.slice(0, maxItems);
1441
+ break;
1442
+ }
1443
+ // Stop if there are no more pages
1444
+ if (!response.has_more) {
1445
+ break;
1446
+ }
1447
+ // Update cursor for next page
1448
+ currentAfter = response.after_id;
1449
+ }
1450
+ return allItems;
1451
+ }
1452
+ /**
1453
+ * Create a paginator iterator for streaming through pages
1454
+ */ function createPaginator(axiosInstance, endpoint, itemSchema, initialOptions = {}) {
1455
+ let currentOptions = {
1456
+ ...initialOptions
1457
+ };
1458
+ let exhausted = false;
1459
+ return {
1460
+ /**
1461
+ * Get the next page
1462
+ */ async next () {
1463
+ if (exhausted) {
1464
+ return {
1465
+ value: {},
1466
+ done: true
1467
+ };
1468
+ }
1469
+ const response = await fetchPaginated(axiosInstance, endpoint, itemSchema, currentOptions);
1470
+ // Update options for next call
1471
+ if (response.has_more && response.after_id) {
1472
+ currentOptions.after_id = response.after_id;
1473
+ } else {
1474
+ exhausted = true;
1475
+ }
1476
+ return {
1477
+ value: response,
1478
+ done: !response.has_more
1479
+ };
1480
+ },
1481
+ /**
1482
+ * Reset the paginator to start from the beginning
1483
+ */ reset (options = {}) {
1484
+ currentOptions = {
1485
+ ...initialOptions,
1486
+ ...options
1487
+ };
1488
+ exhausted = false;
1489
+ },
1490
+ /**
1491
+ * Check if there are more pages available
1492
+ */ hasMore () {
1493
+ return !exhausted;
1494
+ }
1495
+ };
1496
+ }
1497
+ /**
1498
+ * Async iterator for easy for-await-of usage
1499
+ */ async function* iteratePages(axiosInstance, endpoint, itemSchema, options = {}) {
1500
+ const paginator = createPaginator(axiosInstance, endpoint, itemSchema, options);
1501
+ while(paginator.hasMore()){
1502
+ const { value, done } = await paginator.next();
1503
+ if (done) break;
1504
+ yield value;
1505
+ }
1506
+ }
1507
+ /**
1508
+ * Async iterator for individual items across all pages
1509
+ */ async function* iterateItems(axiosInstance, endpoint, itemSchema, options = {}) {
1510
+ let itemCount = 0;
1511
+ const maxItems = options.maxItems || Infinity;
1512
+ for await (const page of iteratePages(axiosInstance, endpoint, itemSchema, options)){
1513
+ for (const item of page.data){
1514
+ if (itemCount >= maxItems) {
1515
+ return;
1516
+ }
1517
+ yield item;
1518
+ itemCount++;
1519
+ }
1520
+ }
1521
+ }
1522
+ function buildSearchParams(options) {
1523
+ const params = buildPaginationParams(options);
1524
+ if (options.query) {
1525
+ params.query = options.query;
1526
+ }
1527
+ if (options.sort_by) {
1528
+ params.sort_by = options.sort_by;
1529
+ }
1530
+ if (options.sort_direction) {
1531
+ params.sort_direction = options.sort_direction;
1532
+ }
1533
+ // Add filter parameters
1534
+ if (options.filters) {
1535
+ Object.entries(options.filters).forEach(([key, value])=>{
1536
+ if (value !== undefined && value !== null) {
1537
+ params[key] = String(value);
1538
+ }
1539
+ });
1540
+ }
1541
+ return params;
1542
+ }
1543
+
1544
+ export { AuthenticationError, NotFoundError, PaginatedResponseSchema, PaginationMetaSchema, RateLimitError, RateLimiter, ValidationError, WebhookEventSchema, WebhookEventTypeSchema, Xendit, XenditApiError, XenditErrorSchema, buildPaginationParams, buildSearchParams, createPaginator, createRateLimitInterceptor, createRateLimitedAxios, createRetryInterceptor, createWebhookProcessor, fetchAllPages, fetchPaginated, handleAxiosError, handleWebhookEvent, isNotNullOrUndefined, isValidCheckoutMethod, isValidCountry, isValidCurrency, isValidCustomerType, isValidDateString, isValidEmail, isValidPhone, isValidUrl, iterateItems, iteratePages, parseWebhookEvent, setupRateLimit, validateInput, verifyWebhookHmac, verifyWebhookSignature };