@raba7ni/raba7ni 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,734 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var axios = require('axios');
6
+ var crypto = require('crypto');
7
+
8
+ /**
9
+ * SDK initialization error
10
+ */
11
+ class Raba7niError extends Error {
12
+ constructor(message, code, statusCode, response) {
13
+ super(message);
14
+ this.code = code;
15
+ this.statusCode = statusCode;
16
+ this.response = response;
17
+ this.name = 'Raba7niError';
18
+ }
19
+ }
20
+ /**
21
+ * Authentication error
22
+ */
23
+ class AuthenticationError extends Raba7niError {
24
+ constructor(message = 'Authentication failed') {
25
+ super(message, 'AUTHENTICATION_ERROR', 401);
26
+ this.name = 'AuthenticationError';
27
+ }
28
+ }
29
+ /**
30
+ * Rate limit error
31
+ */
32
+ class RateLimitError extends Raba7niError {
33
+ constructor(message = 'Rate limit exceeded', retryAfter) {
34
+ super(message, 'RATE_LIMIT_ERROR', 429);
35
+ this.retryAfter = retryAfter;
36
+ this.name = 'RateLimitError';
37
+ }
38
+ }
39
+ /**
40
+ * Validation error
41
+ */
42
+ class ValidationError extends Raba7niError {
43
+ constructor(message, validationErrors) {
44
+ super(message, 'VALIDATION_ERROR', 422);
45
+ this.validationErrors = validationErrors;
46
+ this.name = 'ValidationError';
47
+ }
48
+ }
49
+ /**
50
+ * Network error
51
+ */
52
+ class NetworkError extends Raba7niError {
53
+ constructor(message, originalError) {
54
+ super(message, 'NETWORK_ERROR');
55
+ this.originalError = originalError;
56
+ this.name = 'NetworkError';
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Core API client for Raba7ni Developer API
62
+ */
63
+ class Raba7niClient {
64
+ constructor(config) {
65
+ this.rateLimitInfo = {
66
+ hourlyLimit: 0,
67
+ hourlyRemaining: 0,
68
+ dailyLimit: 0,
69
+ dailyRemaining: 0
70
+ };
71
+ // Set default values
72
+ this.config = {
73
+ baseUrl: config.baseUrl || 'https://api.raba7ni.com',
74
+ locale: config.locale || 'en',
75
+ timeout: config.timeout || 30000,
76
+ maxRetries: config.maxRetries || 3,
77
+ retryDelay: config.retryDelay || 1000,
78
+ ...config
79
+ };
80
+ // Validate required fields
81
+ if (!this.config.appId || !this.config.appId.startsWith('app_')) {
82
+ throw new Error('Invalid appId: must start with "app_"');
83
+ }
84
+ if (!this.config.apiKey || !this.config.apiKey.startsWith('dev_')) {
85
+ throw new Error('Invalid apiKey: must start with "dev_"');
86
+ }
87
+ // Create axios instance
88
+ this.axiosInstance = axios.create({
89
+ baseURL: this.config.baseUrl,
90
+ timeout: this.config.timeout,
91
+ headers: {
92
+ 'Content-Type': 'application/json',
93
+ 'Accept': 'application/json',
94
+ 'X-App-Id': this.config.appId,
95
+ 'X-Api-Key': this.config.apiKey
96
+ }
97
+ });
98
+ // Add request interceptor for logging and debugging
99
+ this.axiosInstance.interceptors.request.use((config) => {
100
+ // Add locale to URL if not already present
101
+ if (!config.url?.includes('/api/')) {
102
+ config.url = `/api/${this.config.locale}/v1/developer${config.url || ''}`;
103
+ }
104
+ return config;
105
+ }, (error) => {
106
+ return Promise.reject(error);
107
+ });
108
+ // Add response interceptor for error handling and rate limit tracking
109
+ this.axiosInstance.interceptors.response.use((response) => {
110
+ this.extractRateLimitInfo(response);
111
+ return response;
112
+ }, (error) => {
113
+ if (error.response) {
114
+ this.extractRateLimitInfo(error.response);
115
+ this.handleApiError(error);
116
+ }
117
+ else if (error.request) {
118
+ throw new NetworkError('Network error: No response received', error);
119
+ }
120
+ else {
121
+ throw new NetworkError('Network error: Request setup failed', error);
122
+ }
123
+ return Promise.reject(error);
124
+ });
125
+ }
126
+ /**
127
+ * Extract rate limit information from response headers
128
+ */
129
+ extractRateLimitInfo(response) {
130
+ const headers = response.headers;
131
+ this.rateLimitInfo = {
132
+ hourlyLimit: parseInt(headers['x-ratelimit-hourly-limit']) || 0,
133
+ hourlyRemaining: parseInt(headers['x-ratelimit-hourly-remaining']) || 0,
134
+ dailyLimit: parseInt(headers['x-ratelimit-daily-limit']) || 0,
135
+ dailyRemaining: parseInt(headers['x-ratelimit-daily-remaining']) || 0,
136
+ resetTime: headers['x-ratelimit-reset']
137
+ };
138
+ }
139
+ /**
140
+ * Handle API errors and convert to appropriate error classes
141
+ */
142
+ handleApiError(error) {
143
+ const status = error.response?.status;
144
+ const data = error.response?.data;
145
+ switch (status) {
146
+ case 401:
147
+ throw new AuthenticationError(data?.message || 'Authentication failed');
148
+ case 403:
149
+ throw new Raba7niError(data?.message || 'Access forbidden', 'FORBIDDEN', 403, data);
150
+ case 404:
151
+ throw new Raba7niError(data?.message || 'Resource not found', 'NOT_FOUND', 404, data);
152
+ case 422:
153
+ throw new ValidationError(data?.message || 'Validation failed', data?.errors);
154
+ case 429:
155
+ const retryAfter = error.response?.headers['retry-after'];
156
+ throw new RateLimitError(data?.message || 'Rate limit exceeded', retryAfter ? parseInt(retryAfter) : undefined);
157
+ case 500:
158
+ case 502:
159
+ case 503:
160
+ case 504:
161
+ throw new Raba7niError(data?.message || 'Server error', 'SERVER_ERROR', status, data);
162
+ default:
163
+ throw new Raba7niError(data?.message || error.message || 'Unknown error', 'UNKNOWN_ERROR', status, data);
164
+ }
165
+ }
166
+ /**
167
+ * Make HTTP request with retry logic
168
+ */
169
+ async request(config) {
170
+ let lastError;
171
+ for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
172
+ try {
173
+ const response = await this.axiosInstance.request(config);
174
+ return response.data;
175
+ }
176
+ catch (error) {
177
+ lastError = error;
178
+ // Don't retry on authentication or validation errors
179
+ if (error instanceof AuthenticationError ||
180
+ error instanceof ValidationError ||
181
+ error instanceof Raba7niError &&
182
+ (error.statusCode === 403 || error.statusCode === 404)) {
183
+ throw error;
184
+ }
185
+ // Retry on rate limit errors with exponential backoff
186
+ if (error instanceof RateLimitError) {
187
+ if (attempt === this.config.maxRetries) {
188
+ throw error;
189
+ }
190
+ const delay = error.retryAfter
191
+ ? error.retryAfter * 1000
192
+ : this.config.retryDelay * Math.pow(2, attempt);
193
+ await this.sleep(delay);
194
+ continue;
195
+ }
196
+ // Retry on network errors
197
+ if (error instanceof NetworkError && attempt < this.config.maxRetries) {
198
+ const delay = this.config.retryDelay * Math.pow(2, attempt);
199
+ await this.sleep(delay);
200
+ continue;
201
+ }
202
+ throw error;
203
+ }
204
+ }
205
+ throw lastError;
206
+ }
207
+ /**
208
+ * Sleep helper for retry delays
209
+ */
210
+ sleep(ms) {
211
+ return new Promise(resolve => {
212
+ if (typeof setTimeout !== 'undefined') {
213
+ setTimeout(resolve, ms);
214
+ }
215
+ else {
216
+ resolve();
217
+ }
218
+ });
219
+ }
220
+ /**
221
+ * Get current rate limit information
222
+ */
223
+ getRateLimitInfo() {
224
+ return { ...this.rateLimitInfo };
225
+ }
226
+ /**
227
+ * Make GET request
228
+ */
229
+ async get(url, config) {
230
+ return this.request({ ...config, method: 'GET', url });
231
+ }
232
+ /**
233
+ * Make POST request
234
+ */
235
+ async post(url, data, config) {
236
+ return this.request({ ...config, method: 'POST', url, data });
237
+ }
238
+ /**
239
+ * Make PUT request
240
+ */
241
+ async put(url, data, config) {
242
+ return this.request({ ...config, method: 'PUT', url, data });
243
+ }
244
+ /**
245
+ * Make PATCH request
246
+ */
247
+ async patch(url, data, config) {
248
+ return this.request({ ...config, method: 'PATCH', url, data });
249
+ }
250
+ /**
251
+ * Make DELETE request
252
+ */
253
+ async delete(url, config) {
254
+ return this.request({ ...config, method: 'DELETE', url });
255
+ }
256
+ /**
257
+ * Update configuration
258
+ */
259
+ updateConfig(config) {
260
+ this.config = { ...this.config, ...config };
261
+ if (config.appId) {
262
+ this.axiosInstance.defaults.headers['X-App-Id'] = config.appId;
263
+ }
264
+ if (config.apiKey) {
265
+ this.axiosInstance.defaults.headers['X-Api-Key'] = config.apiKey;
266
+ }
267
+ if (config.baseUrl) {
268
+ this.axiosInstance.defaults.baseURL = config.baseUrl;
269
+ }
270
+ if (config.timeout) {
271
+ this.axiosInstance.defaults.timeout = config.timeout;
272
+ }
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Utility functions for the Raba7ni SDK
278
+ */
279
+ /**
280
+ * Normalize phone number to E.164 format
281
+ */
282
+ function normalizePhoneNumber(phoneNumber) {
283
+ if (!phoneNumber) {
284
+ throw new Error('Phone number is required');
285
+ }
286
+ // Remove all non-numeric characters except +
287
+ let normalized = phoneNumber.replace(/[^\d+]/g, '');
288
+ // If starts with +, keep it, otherwise assume international format
289
+ if (!normalized.startsWith('+')) {
290
+ // Remove leading zeros
291
+ normalized = normalized.replace(/^0+/, '');
292
+ // If no country code, you might want to add a default one
293
+ // For now, we'll assume the input is already in international format
294
+ // In a real implementation, you might want to use a library like
295
+ // google-libphonenumber to handle country codes properly
296
+ normalized = '+' + normalized;
297
+ }
298
+ // Basic validation
299
+ if (normalized.length < 8 || normalized.length > 15) {
300
+ throw new Error('Invalid phone number format');
301
+ }
302
+ return normalized;
303
+ }
304
+ /**
305
+ * Validate email format
306
+ */
307
+ function validateEmail(email) {
308
+ if (!email) {
309
+ return false;
310
+ }
311
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
312
+ return emailRegex.test(email);
313
+ }
314
+ /**
315
+ * Validate card ID
316
+ */
317
+ function validateCardId(cardId) {
318
+ const id = typeof cardId === 'string' ? parseInt(cardId, 10) : cardId;
319
+ if (isNaN(id) || id <= 0) {
320
+ throw new Error('Card ID must be a positive integer');
321
+ }
322
+ return id;
323
+ }
324
+ /**
325
+ * Validate monetary amount
326
+ */
327
+ function validateAmount(amount) {
328
+ const num = typeof amount === 'string' ? parseFloat(amount) : amount;
329
+ if (isNaN(num) || num < 0) {
330
+ throw new Error('Amount must be a non-negative number');
331
+ }
332
+ // Round to 2 decimal places
333
+ return Math.round(num * 100) / 100;
334
+ }
335
+ /**
336
+ * Sanitize string input
337
+ */
338
+ function sanitizeString(input, maxLength = 255) {
339
+ if (!input) {
340
+ return '';
341
+ }
342
+ // Remove HTML tags and extra whitespace
343
+ let sanitized = input.replace(/<[^>]*>/g, '').trim();
344
+ // Truncate if too long
345
+ if (sanitized.length > maxLength) {
346
+ sanitized = sanitized.substring(0, maxLength);
347
+ }
348
+ return sanitized;
349
+ }
350
+ /**
351
+ * Validate name field
352
+ */
353
+ function validateName(name) {
354
+ if (!name || name.trim().length === 0) {
355
+ throw new Error('Name is required');
356
+ }
357
+ const sanitized = sanitizeString(name.trim(), 100);
358
+ if (sanitized.length < 2) {
359
+ throw new Error('Name must be at least 2 characters long');
360
+ }
361
+ return sanitized;
362
+ }
363
+ /**
364
+ * Deep clone object
365
+ */
366
+ function deepClone(obj) {
367
+ if (obj === null || typeof obj !== 'object') {
368
+ return obj;
369
+ }
370
+ if (obj instanceof Date) {
371
+ return new Date(obj.getTime());
372
+ }
373
+ if (Array.isArray(obj)) {
374
+ return obj.map(item => deepClone(item));
375
+ }
376
+ const cloned = {};
377
+ for (const key in obj) {
378
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
379
+ cloned[key] = deepClone(obj[key]);
380
+ }
381
+ }
382
+ return cloned;
383
+ }
384
+ /**
385
+ * Retry delay calculation with exponential backoff
386
+ */
387
+ function calculateRetryDelay(attempt, baseDelay = 1000) {
388
+ return Math.min(baseDelay * Math.pow(2, attempt), 30000); // Max 30 seconds
389
+ }
390
+ /**
391
+ * Generate random string for request tracking
392
+ */
393
+ function generateRequestId() {
394
+ return Math.random().toString(36).substring(2, 15) +
395
+ Math.random().toString(36).substring(2, 15);
396
+ }
397
+ /**
398
+ * Check if a value is a valid ISO date string
399
+ */
400
+ function isValidISODate(dateString) {
401
+ if (!dateString) {
402
+ return false;
403
+ }
404
+ const date = new Date(dateString);
405
+ return !isNaN(date.getTime()) && dateString.includes('T');
406
+ }
407
+ /**
408
+ * Format date to ISO string
409
+ */
410
+ function formatDate(date) {
411
+ if (typeof date === 'string') {
412
+ return date;
413
+ }
414
+ return date.toISOString();
415
+ }
416
+
417
+ /**
418
+ * Main Raba7ni SDK class
419
+ */
420
+ class Raba7niSDK {
421
+ constructor(config) {
422
+ this.client = new Raba7niClient(config);
423
+ }
424
+ /**
425
+ * Validate if a phone number is a member of a specific card
426
+ */
427
+ async validateMember(cardId, phoneNumber, options = {}) {
428
+ const validatedCardId = validateCardId(cardId);
429
+ const normalizedPhone = normalizePhoneNumber(phoneNumber);
430
+ const request = {
431
+ card_id: validatedCardId,
432
+ phone_number: normalizedPhone,
433
+ include_member_data: options.include_member_data || false
434
+ };
435
+ const response = await this.client.post('validate-member', request);
436
+ return response.data;
437
+ }
438
+ /**
439
+ * Get detailed information about a member for a specific card
440
+ */
441
+ async getMemberDetails(cardId, phoneNumber) {
442
+ const validatedCardId = validateCardId(cardId);
443
+ const normalizedPhone = normalizePhoneNumber(phoneNumber);
444
+ const request = {
445
+ card_id: validatedCardId,
446
+ phone_number: normalizedPhone
447
+ };
448
+ const response = await this.client.post('member-details', request);
449
+ return response.data;
450
+ }
451
+ /**
452
+ * Find an existing member by email/phone, or create a new account.
453
+ * Optionally create an online order and/or award points.
454
+ */
455
+ async findOrCreateMember(request) {
456
+ const validatedRequest = {
457
+ card_id: validateCardId(request.card_id),
458
+ name: validateName(request.name),
459
+ email: sanitizeString(request.email.toLowerCase()),
460
+ phone_number: normalizePhoneNumber(request.phone_number),
461
+ create_order: request.create_order,
462
+ order: request.order ? {
463
+ award_points: request.order.award_points || false,
464
+ total_amount: validateAmount(request.order.total_amount),
465
+ delivery_cost: request.order.delivery_cost
466
+ ? validateAmount(request.order.delivery_cost)
467
+ : undefined,
468
+ items: request.order.items?.map(item => ({
469
+ name: sanitizeString(item.name, 100),
470
+ amount: validateAmount(item.amount),
471
+ metadata: item.metadata || {}
472
+ }))
473
+ } : undefined
474
+ };
475
+ // Validate email format if provided
476
+ if (validatedRequest.email && !validateEmail(validatedRequest.email)) {
477
+ throw new Error('Invalid email format');
478
+ }
479
+ const response = await this.client.post('find-or-create-member', validatedRequest);
480
+ return response.data;
481
+ }
482
+ /**
483
+ * Get card information
484
+ */
485
+ async getCardInfo(cardId) {
486
+ const validatedCardId = validateCardId(cardId);
487
+ const response = await this.client.get(`cards/${validatedCardId}`);
488
+ return response.data;
489
+ }
490
+ /**
491
+ * Get available API scopes
492
+ */
493
+ async getScopes() {
494
+ const response = await this.client.get('scopes');
495
+ return response.data;
496
+ }
497
+ /**
498
+ * Get available webhook events
499
+ */
500
+ async getWebhookEvents() {
501
+ const response = await this.client.get('webhook-events');
502
+ return response.data;
503
+ }
504
+ /**
505
+ * Get current rate limit information
506
+ */
507
+ getRateLimitInfo() {
508
+ return this.client.getRateLimitInfo();
509
+ }
510
+ /**
511
+ * Update SDK configuration
512
+ */
513
+ updateConfig(config) {
514
+ this.client.updateConfig(config);
515
+ }
516
+ /**
517
+ * Helper method to make custom API requests
518
+ */
519
+ async request(method, endpoint, data, options) {
520
+ let response;
521
+ switch (method.toUpperCase()) {
522
+ case 'GET':
523
+ response = await this.client.get(endpoint, options);
524
+ break;
525
+ case 'POST':
526
+ response = await this.client.post(endpoint, data, options);
527
+ break;
528
+ case 'PUT':
529
+ response = await this.client.put(endpoint, data, options);
530
+ break;
531
+ case 'PATCH':
532
+ response = await this.client.patch(endpoint, data, options);
533
+ break;
534
+ case 'DELETE':
535
+ response = await this.client.delete(endpoint, options);
536
+ break;
537
+ default:
538
+ throw new Error(`Unsupported HTTP method: ${method}`);
539
+ }
540
+ return response.data;
541
+ }
542
+ }
543
+
544
+ /**
545
+ * Webhook signature verification utilities
546
+ */
547
+ /**
548
+ * Verify webhook signature
549
+ */
550
+ function verifyWebhookSignature(payload, signature, secret) {
551
+ if (!payload || !signature || !secret) {
552
+ return false;
553
+ }
554
+ try {
555
+ const expectedSignature = crypto.createHmac('sha256', secret)
556
+ .update(payload, 'utf8')
557
+ .digest('hex');
558
+ // Use constant-time comparison to prevent timing attacks
559
+ return crypto.timingSafeEqual(Buffer.from(signature, 'hex'), Buffer.from(expectedSignature, 'hex'));
560
+ }
561
+ catch (error) {
562
+ return false;
563
+ }
564
+ }
565
+ /**
566
+ * Parse webhook payload with type safety
567
+ */
568
+ function parseWebhookPayload(payload) {
569
+ try {
570
+ const parsed = JSON.parse(payload);
571
+ // Validate required fields
572
+ if (!parsed.event || !parsed.timestamp || !parsed.data) {
573
+ throw new Error('Invalid webhook payload: missing required fields');
574
+ }
575
+ // Validate timestamp format
576
+ const timestamp = new Date(parsed.timestamp);
577
+ if (isNaN(timestamp.getTime())) {
578
+ throw new Error('Invalid webhook payload: invalid timestamp format');
579
+ }
580
+ return parsed;
581
+ }
582
+ catch (error) {
583
+ throw new Error(`Failed to parse webhook payload: ${error}`);
584
+ }
585
+ }
586
+ /**
587
+ * Type guard for member joined webhook
588
+ */
589
+ function isMemberJoinedWebhook(payload) {
590
+ return payload.event === 'member_joined';
591
+ }
592
+ /**
593
+ * Type guard for member left webhook
594
+ */
595
+ function isMemberLeftWebhook(payload) {
596
+ return payload.event === 'member_left';
597
+ }
598
+ /**
599
+ * Type guard for points earned webhook
600
+ */
601
+ function isPointsEarnedWebhook(payload) {
602
+ return payload.event === 'points_earned';
603
+ }
604
+ /**
605
+ * Type guard for points redeemed webhook
606
+ */
607
+ function isPointsRedeemedWebhook(payload) {
608
+ return payload.event === 'points_redeemed';
609
+ }
610
+ /**
611
+ * Type guard for transaction created webhook
612
+ */
613
+ function isTransactionCreatedWebhook(payload) {
614
+ return payload.event === 'transaction_created';
615
+ }
616
+ /**
617
+ * Webhook handler class
618
+ */
619
+ class WebhookHandler {
620
+ constructor(secret) {
621
+ if (!secret) {
622
+ throw new Error('Webhook secret is required');
623
+ }
624
+ this.secret = secret;
625
+ }
626
+ /**
627
+ * Verify and parse incoming webhook
628
+ */
629
+ async verifyAndParse(payload, signature) {
630
+ if (!this.verifyWebhookSignature(payload, signature)) {
631
+ throw new Error('Invalid webhook signature');
632
+ }
633
+ return this.parseWebhookPayload(payload);
634
+ }
635
+ /**
636
+ * Verify webhook signature
637
+ */
638
+ verifyWebhookSignature(payload, signature) {
639
+ return verifyWebhookSignature(payload, signature, this.secret);
640
+ }
641
+ /**
642
+ * Parse webhook payload
643
+ */
644
+ parseWebhookPayload(payload) {
645
+ return parseWebhookPayload(payload);
646
+ }
647
+ /**
648
+ * Handle webhook with event-specific callbacks
649
+ */
650
+ async handleWebhook(payload, signature, handlers) {
651
+ const webhook = await this.verifyAndParse(payload, signature);
652
+ try {
653
+ switch (webhook.event) {
654
+ case 'member_joined':
655
+ if (handlers.onMemberJoined && isMemberJoinedWebhook(webhook)) {
656
+ await handlers.onMemberJoined(webhook.data);
657
+ }
658
+ break;
659
+ case 'member_left':
660
+ if (handlers.onMemberLeft && isMemberLeftWebhook(webhook)) {
661
+ await handlers.onMemberLeft(webhook.data);
662
+ }
663
+ break;
664
+ case 'points_earned':
665
+ if (handlers.onPointsEarned && isPointsEarnedWebhook(webhook)) {
666
+ await handlers.onPointsEarned(webhook.data);
667
+ }
668
+ break;
669
+ case 'points_redeemed':
670
+ if (handlers.onPointsRedeemed && isPointsRedeemedWebhook(webhook)) {
671
+ await handlers.onPointsRedeemed(webhook.data);
672
+ }
673
+ break;
674
+ case 'transaction_created':
675
+ if (handlers.onTransactionCreated && isTransactionCreatedWebhook(webhook)) {
676
+ await handlers.onTransactionCreated(webhook.data);
677
+ }
678
+ break;
679
+ default:
680
+ if (handlers.onUnknownEvent) {
681
+ await handlers.onUnknownEvent(webhook.event, webhook.data);
682
+ }
683
+ break;
684
+ }
685
+ }
686
+ catch (error) {
687
+ throw new Error(`Error handling webhook event ${webhook.event}: ${error}`);
688
+ }
689
+ }
690
+ /**
691
+ * Update webhook secret
692
+ */
693
+ updateSecret(secret) {
694
+ if (!secret) {
695
+ throw new Error('Webhook secret is required');
696
+ }
697
+ this.secret = secret;
698
+ }
699
+ /**
700
+ * Get current secret
701
+ */
702
+ getSecret() {
703
+ return this.secret;
704
+ }
705
+ }
706
+
707
+ exports.AuthenticationError = AuthenticationError;
708
+ exports.NetworkError = NetworkError;
709
+ exports.Raba7niClient = Raba7niClient;
710
+ exports.Raba7niError = Raba7niError;
711
+ exports.Raba7niSDK = Raba7niSDK;
712
+ exports.RateLimitError = RateLimitError;
713
+ exports.ValidationError = ValidationError;
714
+ exports.WebhookHandler = WebhookHandler;
715
+ exports.calculateRetryDelay = calculateRetryDelay;
716
+ exports.deepClone = deepClone;
717
+ exports.default = Raba7niSDK;
718
+ exports.formatDate = formatDate;
719
+ exports.generateRequestId = generateRequestId;
720
+ exports.isMemberJoinedWebhook = isMemberJoinedWebhook;
721
+ exports.isMemberLeftWebhook = isMemberLeftWebhook;
722
+ exports.isPointsEarnedWebhook = isPointsEarnedWebhook;
723
+ exports.isPointsRedeemedWebhook = isPointsRedeemedWebhook;
724
+ exports.isTransactionCreatedWebhook = isTransactionCreatedWebhook;
725
+ exports.isValidISODate = isValidISODate;
726
+ exports.normalizePhoneNumber = normalizePhoneNumber;
727
+ exports.parseWebhookPayload = parseWebhookPayload;
728
+ exports.sanitizeString = sanitizeString;
729
+ exports.validateAmount = validateAmount;
730
+ exports.validateCardId = validateCardId;
731
+ exports.validateEmail = validateEmail;
732
+ exports.validateName = validateName;
733
+ exports.verifyWebhookSignature = verifyWebhookSignature;
734
+ //# sourceMappingURL=index.js.map