@duvdu-v1/duvdu 1.1.268 → 1.1.270

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/build/index.d.ts CHANGED
@@ -97,3 +97,4 @@ export * from './events/base-publisher';
97
97
  export * from './events/subject';
98
98
  export * from './events/topic-notification.event';
99
99
  export * from './config/winston';
100
+ export * from './middlewares/pull.connection';
package/build/index.js CHANGED
@@ -115,3 +115,4 @@ __exportStar(require("./events/base-publisher"), exports);
115
115
  __exportStar(require("./events/subject"), exports);
116
116
  __exportStar(require("./events/topic-notification.event"), exports);
117
117
  __exportStar(require("./config/winston"), exports);
118
+ __exportStar(require("./middlewares/pull.connection"), exports);
@@ -0,0 +1,3 @@
1
+ import IORedis from 'ioredis';
2
+ declare const bullRedis: IORedis;
3
+ export { bullRedis };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a, _b, _c;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.bullRedis = void 0;
8
+ // bullmq-redis.ts
9
+ const ioredis_1 = __importDefault(require("ioredis"));
10
+ const bullRedis = new ioredis_1.default({
11
+ host: ((_b = (_a = process.env.REDIS_HOST) === null || _a === void 0 ? void 0 : _a.split('://')[1]) === null || _b === void 0 ? void 0 : _b.split(':')[0]) || 'localhost',
12
+ port: parseInt(((_c = process.env.REDIS_HOST) === null || _c === void 0 ? void 0 : _c.split(':').pop()) || '6379'),
13
+ password: process.env.REDIS_PASS,
14
+ maxRetriesPerRequest: null,
15
+ enableReadyCheck: false,
16
+ retryStrategy: (times) => {
17
+ if (times > 3)
18
+ return null;
19
+ return Math.min(times * 100, 3000);
20
+ },
21
+ });
22
+ exports.bullRedis = bullRedis;
@@ -1,207 +1,21 @@
1
- interface PaymobMerchant {
2
- id: number;
3
- created_at: string;
4
- phones: string[];
5
- company_emails: string[];
6
- company_name: string;
7
- state: string;
8
- country: string;
9
- city: string;
10
- postal_code: string;
11
- street: string;
12
- }
13
- interface PaymobOrder {
14
- id: number;
15
- created_at: string;
16
- delivery_needed: boolean;
17
- merchant: PaymobMerchant;
18
- collector: null;
19
- amount_cents: number;
20
- shipping_data: null;
21
- currency: string;
22
- is_payment_locked: boolean;
23
- is_return: boolean;
24
- is_cancel: boolean;
25
- is_returned: boolean;
26
- is_canceled: boolean;
27
- merchant_order_id: string;
28
- wallet_notification: null;
29
- paid_amount_cents: number;
30
- notify_user_with_email: boolean;
31
- items: PaymobOrderItem[];
32
- order_url: string;
33
- commission_fee: number;
34
- delivery_fee_cents: number;
35
- delivery_voucher_cost: number;
36
- discount: number;
37
- metadata: Record<string, any>;
38
- }
39
- interface PaymobOrderItem {
40
- name: string;
41
- description: string;
42
- amount: number;
43
- quantity: number;
44
- }
45
- interface PaymobSourceData {
46
- type: string;
47
- pan: string;
48
- sub_type: string;
49
- }
50
- interface PaymobWebhookData {
51
- obj: {
52
- id: number;
53
- amount_cents: number;
54
- success: boolean;
55
- is_refunded: boolean;
56
- is_captured: boolean;
57
- is_voided: boolean;
58
- is_standalone_payment: boolean;
59
- is_void: boolean;
60
- is_refund: boolean;
61
- is_3d_secure: boolean;
62
- integration_id: number;
63
- profile_id: number;
64
- has_parent_transaction: boolean;
65
- order: PaymobOrder;
66
- created_at: string;
67
- transaction_processed_callback_responses: null;
68
- currency: string;
69
- source_data: PaymobSourceData;
70
- api_source: string;
71
- terminal_id: string;
72
- merchant_commission: number;
73
- merchant_staff_tag: null;
74
- hmac: string;
75
- };
76
- }
77
- interface PaymobBillingData {
78
- first_name: string;
79
- last_name: string;
80
- email: string;
81
- phone_number: string;
82
- apartment: string;
83
- floor: string;
84
- street: string;
85
- building: string;
86
- city?: string;
87
- state: string;
88
- country: string;
89
- }
90
- interface TransactionData {
91
- orderId: number;
92
- amount: number;
93
- success: boolean;
94
- currency: string;
95
- transactionId: number;
96
- createdAt: string;
97
- isRefunded: boolean;
98
- isCaptured: boolean;
99
- isVoided: boolean;
100
- metadata: Record<string, any>;
101
- }
102
- interface WebhookQueryTransactionData extends TransactionData {
103
- isAuth: boolean;
104
- isStandalone: boolean;
105
- is3dSecure: boolean;
106
- sourceData: {
107
- type: string;
108
- pan: string;
109
- subType: string;
110
- };
111
- responseCode: string;
112
- message: string;
113
- }
114
- interface WebhookQueryWithItemsTransactionData extends WebhookQueryTransactionData {
115
- items: PaymobOrderItem[];
116
- }
117
- interface WebhookResult<T> {
118
- isValid: boolean;
119
- transactionData: T | null;
120
- }
121
- interface TransactionStatusResult {
122
- success: boolean;
123
- status: string;
124
- amount: number;
125
- currency: string;
126
- }
127
- interface OrderDetailsResult {
128
- id: number;
129
- amount_cents: number;
130
- currency: string;
131
- items: PaymobOrderItem[];
132
- created_at: string;
133
- merchant_order_id: string;
134
- }
135
- /**
136
- * Paymob Service Configuration for Flash Integration
137
- *
138
- * Required Keys:
139
- * - secretKey: Your Paymob Secret Key (for API authentication)
140
- * - publicKey: Your Paymob Public Key (for client-side)
141
- * - integrationId: Your Paymob Integration ID
142
- * - hmacSecret: Your Paymob HMAC Secret (for webhook verification)
143
- */
1
+ import { PaymobWebhookData, WebhookResult, TransactionData, WebhookQueryTransactionData, OrderDetailsResult, TransactionStatusResult, PaymentIntentionResult, UserPaymentData, PaymobBillingData, PaymobOrderItem, WebhookQueryWithItemsTransactionData } from '../types/paymob.types';
144
2
  export declare class PaymobService {
145
- private readonly secretKey;
146
- private readonly publicKey;
147
- private readonly integrationId;
148
- private readonly baseUrl;
149
- private readonly hmacSecret;
3
+ private readonly config;
4
+ private readonly auth;
5
+ private readonly webhookHandler;
6
+ private readonly orderManager;
7
+ private readonly paymentProcessor;
150
8
  constructor();
151
- /**
152
- * Creates a payment intention using the Flash API
153
- * @param amount The payment amount
154
- * @param billingData The user data for billing
155
- * @param items The order items
156
- * @param currency The currency code (default: EGP)
157
- * @param extras Custom metadata
158
- * @returns The payment URL and client secret
159
- */
160
- createPaymentIntention(amount: number, billingData: PaymobBillingData, items: PaymobOrderItem[], currency?: string, extras?: Record<string, any>): Promise<{
161
- paymentUrl: string;
162
- clientSecret: string;
163
- }>;
164
- /**
165
- * Creates a payment URL with user data and custom metadata
166
- * @param amount The payment amount
167
- * @param userId The user ID
168
- * @param contractId The contract ID
169
- * @param userData The user data for billing
170
- * @param serviceType The service type identifier
171
- * @returns The payment URL and related data
172
- */
173
- createPaymentUrlWithUserData(amount: number, userId: string, contractId: string, userData: {
174
- firstName: string;
175
- lastName: string;
176
- email: string;
177
- phone: string;
178
- }, serviceType: string): Promise<{
9
+ createPaymentIntention(amount: number, billingData: PaymobBillingData, items: PaymobOrderItem[], currency?: string, extras?: Record<string, any>): Promise<PaymentIntentionResult>;
10
+ createPaymentUrlWithUserData(amount: number, userId: string, contractId: string, userData: UserPaymentData, serviceType: string): Promise<{
179
11
  paymentUrl: string;
180
12
  }>;
181
13
  verifyPayment(hmac: string, data: Record<string, any>): Promise<boolean>;
182
- /**
183
- * Verify webhook HMAC signature for query parameters
184
- * Uses the correct field order as per Paymob documentation
185
- */
186
14
  verifyWebhookHmac(queryParams: Record<string, string>): boolean;
187
15
  handleWebhook(webhookData: PaymobWebhookData): Promise<WebhookResult<TransactionData>>;
188
- /**
189
- * Handle webhook with query parameters (GET request)
190
- * This is for webhooks that come as URL query parameters instead of JSON body
191
- */
192
16
  handleWebhookQuery(queryParams: Record<string, string>): WebhookResult<WebhookQueryTransactionData>;
17
+ handleWebhookQueryWithItems(queryParams: Record<string, string>): Promise<WebhookResult<WebhookQueryWithItemsTransactionData>>;
193
18
  getTransactionStatus(transactionId: number): Promise<TransactionStatusResult>;
194
- /**
195
- * Authenticate with Paymob to get Bearer token
196
- */
197
- private authenticate;
198
- /**
199
- * Get order details including metadata using the correct Paymob API
200
- */
201
19
  getOrderDetails(orderId: number): Promise<OrderDetailsResult>;
202
- /**
203
- * Handle webhook query and fetch items from order
204
- */
205
- handleWebhookQueryWithItems(queryParams: Record<string, string>): Promise<WebhookResult<WebhookQueryWithItemsTransactionData>>;
20
+ getAuthToken(): Promise<string>;
206
21
  }
207
- export {};
@@ -38,171 +38,108 @@ Object.defineProperty(exports, "__esModule", { value: true });
38
38
  exports.PaymobService = void 0;
39
39
  const crypto = __importStar(require("crypto"));
40
40
  const axios_1 = __importDefault(require("axios"));
41
- /**
42
- * Paymob Service Configuration for Flash Integration
43
- *
44
- * Required Keys:
45
- * - secretKey: Your Paymob Secret Key (for API authentication)
46
- * - publicKey: Your Paymob Public Key (for client-side)
47
- * - integrationId: Your Paymob Integration ID
48
- * - hmacSecret: Your Paymob HMAC Secret (for webhook verification)
49
- */
50
- class PaymobService {
41
+ class PaymobConfig {
51
42
  constructor() {
43
+ this.validateEnvironmentVariables();
44
+ this.apiKey = process.env.PAYMOB_API_KEY;
52
45
  this.secretKey = process.env.PAYMOB_SECRET_KEY;
53
46
  this.publicKey = process.env.PAYMOB_PUBLIC_KEY;
54
47
  this.integrationId = parseInt(process.env.PAYMOB_INTEGRATION_ID);
55
48
  this.baseUrl = process.env.PAYMOB_BASE_URL || 'https://accept.paymob.com';
56
49
  this.hmacSecret = process.env.PAYMOB_HMAC_SECRET;
57
- console.log('PayMob configuration:', {
50
+ this.logConfiguration();
51
+ }
52
+ /**
53
+ * Validates that all required environment variables are present
54
+ */
55
+ validateEnvironmentVariables() {
56
+ const requiredVars = [
57
+ 'PAYMOB_API_KEY',
58
+ 'PAYMOB_SECRET_KEY',
59
+ 'PAYMOB_PUBLIC_KEY',
60
+ 'PAYMOB_INTEGRATION_ID',
61
+ 'PAYMOB_HMAC_SECRET',
62
+ ];
63
+ const missingVars = requiredVars.filter((varName) => !process.env[varName]);
64
+ if (missingVars.length > 0) {
65
+ throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`);
66
+ }
67
+ }
68
+ /**
69
+ * Logs configuration details (without sensitive data)
70
+ */
71
+ logConfiguration() {
72
+ console.log('PayMob configuration initialized:', {
58
73
  integrationId: this.integrationId,
59
74
  publicKey: this.publicKey,
60
75
  baseUrl: this.baseUrl,
61
76
  });
62
77
  }
78
+ }
79
+ // ===========================
80
+ // AUTHENTICATION CLASS
81
+ // ===========================
82
+ /**
83
+ * Handles Paymob authentication operations
84
+ */
85
+ class PaymobAuth {
86
+ constructor(config) {
87
+ this.config = config;
88
+ }
63
89
  /**
64
- * Creates a payment intention using the Flash API
65
- * @param amount The payment amount
66
- * @param billingData The user data for billing
67
- * @param items The order items
68
- * @param currency The currency code (default: EGP)
69
- * @param extras Custom metadata
70
- * @returns The payment URL and client secret
90
+ * Authenticate with Paymob to get Bearer token
91
+ * @returns Promise<string> Authentication token
71
92
  */
72
- createPaymentIntention(amount, billingData, items, currency = 'EGP', extras) {
73
- var _a;
93
+ getAuthToken() {
74
94
  return __awaiter(this, void 0, void 0, function* () {
75
95
  try {
76
- const intentionData = {
77
- amount,
78
- currency,
79
- payment_methods: [this.integrationId, 'card'],
80
- items,
81
- billing_data: billingData,
82
- customer: {
83
- first_name: billingData.first_name,
84
- last_name: billingData.last_name,
85
- email: billingData.email,
86
- extras: extras || {},
87
- },
88
- extras: extras || {},
89
- };
90
- const response = yield axios_1.default.post(`${this.baseUrl}/v1/intention/`, intentionData, {
91
- headers: {
92
- 'Authorization': `Token ${this.secretKey}`,
93
- 'Content-Type': 'application/json',
94
- },
96
+ const response = yield axios_1.default.post(`${this.config.baseUrl}/api/auth/tokens`, {
97
+ api_key: this.config.apiKey,
95
98
  });
96
- // Create the payment URL for Flash Checkout
97
- const paymentUrl = `${this.baseUrl}/unifiedcheckout/?publicKey=${this.publicKey}&clientSecret=${response.data.client_secret}`;
98
- return {
99
- paymentUrl,
100
- clientSecret: response.data.client_secret,
101
- };
99
+ return response.data.token;
102
100
  }
103
101
  catch (error) {
104
102
  const axiosError = error;
105
- console.log('PayMob intention error:', (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.data);
106
- throw new Error(`Failed to create Paymob payment intention: ${axiosError.message}`);
103
+ throw new Error(`Failed to get Paymob auth token: ${axiosError.message}`);
107
104
  }
108
105
  });
109
106
  }
110
107
  /**
111
- * Creates a payment URL with user data and custom metadata
112
- * @param amount The payment amount
113
- * @param userId The user ID
114
- * @param contractId The contract ID
115
- * @param userData The user data for billing
116
- * @param serviceType The service type identifier
117
- * @returns The payment URL and related data
108
+ * Get authorization headers for API requests
109
+ * @returns Promise<Record<string, string>> Headers object
118
110
  */
119
- createPaymentUrlWithUserData(amount, userId, contractId, userData, serviceType) {
120
- var _a;
111
+ getAuthHeaders() {
121
112
  return __awaiter(this, void 0, void 0, function* () {
122
- // Create metadata with custom data
123
- const customData = {
124
- contractId,
125
- userId,
126
- service_type: serviceType,
127
- booking_id: 'BOOK_' + Date.now(),
128
- timestamp: new Date().toISOString(),
113
+ return {
114
+ Authorization: `Token ${this.config.secretKey}`,
115
+ 'Content-Type': 'application/json',
129
116
  };
130
- const extras = customData;
131
- const billingData = {
132
- first_name: userData.firstName,
133
- last_name: userData.lastName,
134
- email: userData.email,
135
- phone_number: userData.phone,
136
- apartment: '123',
137
- floor: '1',
138
- street: '123 Main St',
139
- building: '123',
140
- state: 'Cairo',
141
- country: 'EGY',
142
- };
143
- const items = [
144
- {
145
- name: `${serviceType} Payment`,
146
- description: `Payment for contract ${contractId}`,
147
- amount,
148
- quantity: 1,
149
- },
150
- ];
151
- // For Flash Integration, we need to create a modified intention request
152
- // that includes merchant_order_id
153
- const intentionData = {
154
- amount,
155
- currency: 'EGP',
156
- payment_methods: [this.integrationId, 'card'],
157
- items,
158
- billing_data: billingData,
159
- customer: {
160
- first_name: billingData.first_name,
161
- last_name: billingData.last_name,
162
- email: billingData.email,
163
- extras: extras || {},
164
- },
165
- extras: extras || {},
166
- merchant_order_id: JSON.stringify(customData), // Store custom data here
167
- };
168
- try {
169
- const response = yield axios_1.default.post(`${this.baseUrl}/v1/intention/`, intentionData, {
170
- headers: {
171
- 'Authorization': `Token ${this.secretKey}`,
172
- 'Content-Type': 'application/json',
173
- },
174
- });
175
- // Create the payment URL for Flash Checkout
176
- const paymentUrl = `${this.baseUrl}/unifiedcheckout/?publicKey=${this.publicKey}&clientSecret=${response.data.client_secret}`;
177
- return { paymentUrl };
178
- }
179
- catch (error) {
180
- const axiosError = error;
181
- console.log('PayMob intention error:', (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.data);
182
- throw new Error(`Failed to create Paymob payment intention: ${axiosError.message}`);
183
- }
184
117
  });
185
118
  }
186
- verifyPayment(hmac, data) {
119
+ /**
120
+ * Get bearer token headers for API requests
121
+ * @returns Promise<Record<string, string>> Headers object with bearer token
122
+ */
123
+ getBearerHeaders() {
187
124
  return __awaiter(this, void 0, void 0, function* () {
188
- const concatenatedString = Object.entries(data)
189
- .sort()
190
- .map(([key, value]) => `${key}=${value}`)
191
- .join('');
192
- const calculatedHmac = crypto
193
- .createHmac('sha512', this.secretKey)
194
- .update(concatenatedString)
195
- .digest('hex');
196
- return calculatedHmac === hmac;
125
+ const authToken = yield this.getAuthToken();
126
+ return {
127
+ 'Content-Type': 'application/json',
128
+ Authorization: `Bearer ${authToken}`,
129
+ };
197
130
  });
198
131
  }
199
- /**
200
- * Verify webhook HMAC signature for query parameters
201
- * Uses the correct field order as per Paymob documentation
202
- */
203
- verifyWebhookHmac(queryParams) {
204
- // Define the exact order of fields for HMAC calculation as per Paymob docs
205
- const orderedFields = [
132
+ }
133
+ // ===========================
134
+ // WEBHOOK HANDLER CLASS
135
+ // ===========================
136
+ /**
137
+ * Handles Paymob webhook operations and verification
138
+ */
139
+ class PaymobWebhookHandler {
140
+ constructor(config) {
141
+ this.config = config;
142
+ this.WEBHOOK_FIELD_ORDER = [
206
143
  'amount_cents',
207
144
  'created_at',
208
145
  'currency',
@@ -224,36 +161,53 @@ class PaymobService {
224
161
  'source_data.type',
225
162
  'success',
226
163
  ];
227
- // Concatenate values in the exact order (no keys, just values)
228
- const concatenatedString = orderedFields.map((field) => queryParams[field] || '').join('');
229
- // Calculate HMAC using the secret key
164
+ }
165
+ /**
166
+ * Verify payment using HMAC signature
167
+ * @param hmac HMAC signature to verify
168
+ * @param data Payment data to verify
169
+ * @returns Promise<boolean> Verification result
170
+ */
171
+ verifyPayment(hmac, data) {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ const concatenatedString = Object.entries(data)
174
+ .sort()
175
+ .map(([key, value]) => `${key}=${value}`)
176
+ .join('');
177
+ const calculatedHmac = crypto
178
+ .createHmac('sha512', this.config.secretKey)
179
+ .update(concatenatedString)
180
+ .digest('hex');
181
+ return calculatedHmac === hmac;
182
+ });
183
+ }
184
+ /**
185
+ * Verify webhook HMAC signature for query parameters
186
+ * Uses the correct field order as per Paymob documentation
187
+ * @param queryParams Query parameters from webhook
188
+ * @returns boolean Verification result
189
+ */
190
+ verifyWebhookHmac(queryParams) {
191
+ const concatenatedString = this.WEBHOOK_FIELD_ORDER.map((field) => queryParams[field] || '').join('');
230
192
  const calculatedHmac = crypto
231
- .createHmac('sha512', this.hmacSecret)
193
+ .createHmac('sha512', this.config.hmacSecret)
232
194
  .update(concatenatedString)
233
195
  .digest('hex');
234
196
  return calculatedHmac === queryParams.hmac;
235
197
  }
198
+ /**
199
+ * Handle webhook with JSON data (POST request)
200
+ * @param webhookData Webhook data from Paymob
201
+ * @returns Promise<WebhookResult<TransactionData>> Webhook processing result
202
+ */
236
203
  handleWebhook(webhookData) {
237
204
  return __awaiter(this, void 0, void 0, function* () {
238
205
  try {
239
- // Verify the webhook signature
240
206
  const isValid = yield this.verifyPayment(webhookData.obj.hmac, webhookData.obj);
241
207
  if (!isValid) {
242
208
  return { isValid: false, transactionData: null };
243
209
  }
244
- // Extract relevant transaction data
245
- const transactionData = {
246
- orderId: webhookData.obj.order.id,
247
- amount: webhookData.obj.amount_cents / 100, // Convert from cents to actual currency
248
- success: webhookData.obj.success,
249
- currency: webhookData.obj.currency,
250
- transactionId: webhookData.obj.id,
251
- createdAt: webhookData.obj.created_at,
252
- isRefunded: webhookData.obj.is_refunded,
253
- isCaptured: webhookData.obj.is_captured,
254
- isVoided: webhookData.obj.is_voided,
255
- metadata: webhookData.obj.order.metadata || {},
256
- };
210
+ const transactionData = this.extractTransactionData(webhookData);
257
211
  return { isValid: true, transactionData };
258
212
  }
259
213
  catch (error) {
@@ -264,38 +218,16 @@ class PaymobService {
264
218
  }
265
219
  /**
266
220
  * Handle webhook with query parameters (GET request)
267
- * This is for webhooks that come as URL query parameters instead of JSON body
221
+ * @param queryParams Query parameters from webhook URL
222
+ * @returns WebhookResult<WebhookQueryTransactionData> Webhook processing result
268
223
  */
269
224
  handleWebhookQuery(queryParams) {
270
225
  try {
271
- // Verify the webhook HMAC signature
272
226
  const isValid = this.verifyWebhookHmac(queryParams);
273
227
  if (!isValid) {
274
228
  return { isValid: false, transactionData: null };
275
229
  }
276
- // Extract and parse transaction data from query parameters
277
- const transactionData = {
278
- orderId: parseInt(queryParams.order || '0'),
279
- amount: parseInt(queryParams.amount_cents || '0') / 100, // Convert from cents
280
- success: queryParams.success === 'true',
281
- currency: queryParams.currency || 'EGP',
282
- transactionId: parseInt(queryParams.id || '0'),
283
- createdAt: queryParams.created_at || '',
284
- isRefunded: queryParams.is_refunded === 'true',
285
- isCaptured: queryParams.is_capture === 'true',
286
- isVoided: queryParams.is_voided === 'true',
287
- isAuth: queryParams.is_auth === 'true',
288
- isStandalone: queryParams.is_standalone_payment === 'true',
289
- is3dSecure: queryParams.is_3d_secure === 'true',
290
- sourceData: {
291
- type: queryParams['source_data.type'] || '',
292
- pan: queryParams['source_data.pan'] || '',
293
- subType: queryParams['source_data.sub_type'] || '',
294
- },
295
- responseCode: queryParams.txn_response_code || '',
296
- message: queryParams['data.message'] || '',
297
- metadata: {}, // Adding the missing metadata property
298
- };
230
+ const transactionData = this.extractQueryTransactionData(queryParams);
299
231
  return { isValid: true, transactionData };
300
232
  }
301
233
  catch (error) {
@@ -303,15 +235,99 @@ class PaymobService {
303
235
  throw new Error(`Failed to handle webhook query: ${errorMessage}`);
304
236
  }
305
237
  }
238
+ /**
239
+ * Extract transaction data from webhook JSON data
240
+ * @private
241
+ */
242
+ extractTransactionData(webhookData) {
243
+ return {
244
+ orderId: webhookData.obj.order.id,
245
+ amount: webhookData.obj.amount_cents / 100,
246
+ success: webhookData.obj.success,
247
+ currency: webhookData.obj.currency,
248
+ transactionId: webhookData.obj.id,
249
+ createdAt: webhookData.obj.created_at,
250
+ isRefunded: webhookData.obj.is_refunded,
251
+ isCaptured: webhookData.obj.is_captured,
252
+ isVoided: webhookData.obj.is_voided,
253
+ metadata: webhookData.obj.order.metadata || {},
254
+ };
255
+ }
256
+ /**
257
+ * Extract transaction data from webhook query parameters
258
+ * @private
259
+ */
260
+ extractQueryTransactionData(queryParams) {
261
+ return {
262
+ orderId: parseInt(queryParams.order || '0'),
263
+ amount: parseInt(queryParams.amount_cents || '0') / 100,
264
+ success: queryParams.success === 'true',
265
+ currency: queryParams.currency || 'EGP',
266
+ transactionId: parseInt(queryParams.id || '0'),
267
+ createdAt: queryParams.created_at || '',
268
+ isRefunded: queryParams.is_refunded === 'true',
269
+ isCaptured: queryParams.is_capture === 'true',
270
+ isVoided: queryParams.is_voided === 'true',
271
+ isAuth: queryParams.is_auth === 'true',
272
+ isStandalone: queryParams.is_standalone_payment === 'true',
273
+ is3dSecure: queryParams.is_3d_secure === 'true',
274
+ sourceData: {
275
+ type: queryParams['source_data.type'] || '',
276
+ pan: queryParams['source_data.pan'] || '',
277
+ subType: queryParams['source_data.sub_type'] || '',
278
+ },
279
+ responseCode: queryParams.txn_response_code || '',
280
+ message: queryParams['data.message'] || '',
281
+ metadata: {},
282
+ };
283
+ }
284
+ }
285
+ // ===========================
286
+ // ORDER MANAGER CLASS
287
+ // ===========================
288
+ /**
289
+ * Handles Paymob order operations
290
+ */
291
+ class PaymobOrderManager {
292
+ constructor(config, auth) {
293
+ this.config = config;
294
+ this.auth = auth;
295
+ }
296
+ /**
297
+ * Get order details including metadata
298
+ * @param orderId Order ID to fetch details for
299
+ * @returns Promise<OrderDetailsResult> Order details
300
+ */
301
+ getOrderDetails(orderId) {
302
+ return __awaiter(this, void 0, void 0, function* () {
303
+ try {
304
+ const headers = yield this.auth.getBearerHeaders();
305
+ const response = yield axios_1.default.get(`${this.config.baseUrl}/api/ecommerce/orders/${orderId}`, { headers });
306
+ return {
307
+ id: response.data.id,
308
+ amount_cents: response.data.amount_cents,
309
+ currency: response.data.currency,
310
+ items: response.data.items || [],
311
+ created_at: response.data.created_at,
312
+ merchant_order_id: response.data.merchant_order_id,
313
+ };
314
+ }
315
+ catch (error) {
316
+ const axiosError = error;
317
+ throw new Error(`Failed to get order details: ${axiosError.message}`);
318
+ }
319
+ });
320
+ }
321
+ /**
322
+ * Get transaction status by transaction ID
323
+ * @param transactionId Transaction ID to check status for
324
+ * @returns Promise<TransactionStatusResult> Transaction status
325
+ */
306
326
  getTransactionStatus(transactionId) {
307
327
  return __awaiter(this, void 0, void 0, function* () {
308
328
  try {
309
- const response = yield axios_1.default.get(`${this.baseUrl}/api/acceptance/transactions/${transactionId}`, {
310
- headers: {
311
- 'Content-Type': 'application/json',
312
- 'Authorization': `Token ${this.secretKey}`,
313
- },
314
- });
329
+ const headers = yield this.auth.getAuthHeaders();
330
+ const response = yield axios_1.default.get(`${this.config.baseUrl}/api/acceptance/transactions/${transactionId}`, { headers });
315
331
  return {
316
332
  success: response.data.success,
317
333
  status: response.data.status,
@@ -325,93 +341,203 @@ class PaymobService {
325
341
  }
326
342
  });
327
343
  }
344
+ }
345
+ // ===========================
346
+ // PAYMENT PROCESSOR CLASS
347
+ // ===========================
348
+ /**
349
+ * Handles Paymob payment processing operations
350
+ */
351
+ class PaymobPaymentProcessor {
352
+ constructor(config, auth) {
353
+ this.config = config;
354
+ this.auth = auth;
355
+ }
328
356
  /**
329
- * Authenticate with Paymob to get Bearer token
357
+ * Create payment intention with Paymob
358
+ * @param amount Payment amount
359
+ * @param billingData Customer billing information
360
+ * @param items Order items
361
+ * @param currency Payment currency (default: EGP)
362
+ * @param extras Additional metadata
363
+ * @returns Promise<PaymentIntentionResult> Payment URL and client secret
330
364
  */
331
- authenticate() {
332
- var _a;
365
+ createPaymentIntention(amount, billingData, items, currency = 'EGP', extras) {
333
366
  return __awaiter(this, void 0, void 0, function* () {
334
367
  try {
335
- const response = yield axios_1.default.post(`${this.baseUrl}/api/auth/tokens`, {
336
- api_key: this.secretKey,
337
- }, {
338
- headers: {
339
- 'Content-Type': 'application/json',
340
- },
341
- });
342
- return response.data.token;
368
+ const intentionData = this.buildIntentionRequest(amount, billingData, items, currency, extras);
369
+ const headers = yield this.auth.getAuthHeaders();
370
+ const response = yield axios_1.default.post(`${this.config.baseUrl}/v1/intention/`, intentionData, { headers });
371
+ return this.buildPaymentResult(response.data.client_secret);
343
372
  }
344
373
  catch (error) {
345
374
  const axiosError = error;
346
- console.error('Authentication error:', (_a = axiosError.response) === null || _a === void 0 ? void 0 : _a.data);
347
- throw new Error(`Failed to authenticate with Paymob: ${axiosError.message}`);
375
+ console.log('=======================');
376
+ console.log('axiosError', error);
377
+ console.log('=======================');
378
+ throw new Error(`Failed to create Paymob payment intention: ${axiosError.message}`);
348
379
  }
349
380
  });
350
381
  }
351
382
  /**
352
- * Get order details including metadata using the correct Paymob API
383
+ * Create payment URL with user data for contracts
384
+ * @param amount Payment amount
385
+ * @param userId User ID
386
+ * @param contractId Contract ID
387
+ * @param userData User data for billing
388
+ * @param serviceType Type of service being paid for
389
+ * @returns Promise<{paymentUrl: string}> Payment URL
353
390
  */
354
- getOrderDetails(orderId) {
355
- var _a, _b, _c, _d, _e, _f;
391
+ createPaymentUrlWithUserData(amount, userId, contractId, userData, serviceType) {
356
392
  return __awaiter(this, void 0, void 0, function* () {
393
+ const customData = this.buildCustomMetadata(contractId, userId, serviceType);
394
+ const billingData = this.buildBillingDataFromUser(userData);
395
+ const items = this.buildOrderItems(userId, contractId, serviceType, amount);
357
396
  try {
358
- // Step 1: Authenticate to get Bearer token
359
- const authToken = yield this.authenticate();
360
- // Step 2: Get transaction details using the order inquiry endpoint
361
- const response = yield axios_1.default.post(`${this.baseUrl}/api/ecommerce/orders/transaction_inquiry`, {
362
- order_id: orderId.toString(),
363
- }, {
364
- headers: {
365
- 'Content-Type': 'application/json',
366
- 'Authorization': `Bearer ${authToken}`,
367
- },
368
- });
369
- // The response might contain transaction data, we need to extract order info
370
- const transactionData = response.data;
371
- return {
372
- id: orderId,
373
- amount_cents: transactionData.amount_cents || 0,
374
- currency: transactionData.currency || 'EGP',
375
- items: ((_a = transactionData.order) === null || _a === void 0 ? void 0 : _a.items) || [],
376
- created_at: transactionData.created_at || new Date().toISOString(),
377
- merchant_order_id: ((_b = transactionData.order) === null || _b === void 0 ? void 0 : _b.merchant_order_id) || '',
378
- };
397
+ const intentionData = this.buildIntentionRequest(amount, billingData, items, 'EGP', customData);
398
+ console.log('=======================');
399
+ console.log('intentionData', intentionData);
400
+ console.log('=======================');
401
+ const headers = yield this.auth.getAuthHeaders();
402
+ const response = yield axios_1.default.post(`${this.config.baseUrl}/v1/intention/`, intentionData, { headers });
403
+ const paymentUrl = this.buildPaymentUrl(response.data.client_secret);
404
+ return { paymentUrl };
379
405
  }
380
406
  catch (error) {
381
407
  const axiosError = error;
382
- console.error('Order details error response:', (_c = axiosError.response) === null || _c === void 0 ? void 0 : _c.data);
383
- console.error('Order details error status:', (_d = axiosError.response) === null || _d === void 0 ? void 0 : _d.status);
384
- // If still failing, provide a fallback that returns minimal data
385
- if (((_e = axiosError.response) === null || _e === void 0 ? void 0 : _e.status) === 401 || ((_f = axiosError.response) === null || _f === void 0 ? void 0 : _f.status) === 403) {
386
- console.log('Authentication failed, using fallback approach...');
387
- // Return a minimal response that won't break the webhook
388
- return {
389
- id: orderId,
390
- amount_cents: 0,
391
- currency: 'EGP',
392
- items: [],
393
- created_at: new Date().toISOString(),
394
- merchant_order_id: '', // Empty merchant_order_id will trigger fallback logic
395
- };
396
- }
397
- throw new Error(`Failed to get order details: ${axiosError.message}`);
408
+ console.log('=======================');
409
+ console.log('axiosError', error);
410
+ console.log('=======================');
411
+ throw new Error(`Failed to create Paymob payment intention: ${axiosError.message}`);
398
412
  }
399
413
  });
400
414
  }
401
415
  /**
402
- * Handle webhook query and fetch items from order
416
+ * Build payment intention request data
417
+ * @private
418
+ */
419
+ buildIntentionRequest(amount, billingData, items, currency, extras) {
420
+ const amountInCents = amount * 100;
421
+ return {
422
+ amount: amountInCents,
423
+ currency,
424
+ payment_methods: [this.config.integrationId, 'card'],
425
+ items,
426
+ billing_data: billingData,
427
+ customer: {
428
+ first_name: billingData.first_name,
429
+ last_name: billingData.last_name,
430
+ email: billingData.email,
431
+ extras: extras || {},
432
+ },
433
+ extras: extras || {},
434
+ };
435
+ }
436
+ /**
437
+ * Build custom metadata for contract payments
438
+ * @private
439
+ */
440
+ buildCustomMetadata(contractId, userId, serviceType) {
441
+ return {
442
+ contractId,
443
+ userId,
444
+ service_type: serviceType,
445
+ booking_id: 'BOOK_' + Date.now(),
446
+ timestamp: new Date().toISOString(),
447
+ };
448
+ }
449
+ /**
450
+ * Build billing data from user data
451
+ * @private
452
+ */
453
+ buildBillingDataFromUser(userData) {
454
+ return {
455
+ first_name: userData.firstName,
456
+ last_name: userData.lastName,
457
+ email: userData.email,
458
+ phone_number: userData.phone,
459
+ apartment: '123',
460
+ floor: '1',
461
+ street: '123 Main St',
462
+ building: '123',
463
+ state: 'Cairo',
464
+ country: 'EGY',
465
+ };
466
+ }
467
+ /**
468
+ * Build order items for contract payment
469
+ * @private
470
+ */
471
+ buildOrderItems(userId, contractId, serviceType, amount) {
472
+ return [
473
+ {
474
+ name: `${userId}-${contractId}`,
475
+ description: serviceType,
476
+ amount: amount * 100,
477
+ quantity: 1,
478
+ },
479
+ ];
480
+ }
481
+ /**
482
+ * Build payment URL from client secret
483
+ * @private
403
484
  */
485
+ buildPaymentUrl(clientSecret) {
486
+ return `${this.config.baseUrl}/unifiedcheckout/?publicKey=${this.config.publicKey}&clientSecret=${clientSecret}`;
487
+ }
488
+ /**
489
+ * Build payment result with URL and client secret
490
+ * @private
491
+ */
492
+ buildPaymentResult(clientSecret) {
493
+ return {
494
+ paymentUrl: this.buildPaymentUrl(clientSecret),
495
+ clientSecret,
496
+ };
497
+ }
498
+ }
499
+ class PaymobService {
500
+ constructor() {
501
+ this.config = new PaymobConfig();
502
+ this.auth = new PaymobAuth(this.config);
503
+ this.webhookHandler = new PaymobWebhookHandler(this.config);
504
+ this.orderManager = new PaymobOrderManager(this.config, this.auth);
505
+ this.paymentProcessor = new PaymobPaymentProcessor(this.config, this.auth);
506
+ }
507
+ createPaymentIntention(amount, billingData, items, currency = 'EGP', extras) {
508
+ return __awaiter(this, void 0, void 0, function* () {
509
+ return this.paymentProcessor.createPaymentIntention(amount, billingData, items, currency, extras);
510
+ });
511
+ }
512
+ createPaymentUrlWithUserData(amount, userId, contractId, userData, serviceType) {
513
+ return __awaiter(this, void 0, void 0, function* () {
514
+ return this.paymentProcessor.createPaymentUrlWithUserData(amount, userId, contractId, userData, serviceType);
515
+ });
516
+ }
517
+ verifyPayment(hmac, data) {
518
+ return __awaiter(this, void 0, void 0, function* () {
519
+ return this.webhookHandler.verifyPayment(hmac, data);
520
+ });
521
+ }
522
+ verifyWebhookHmac(queryParams) {
523
+ return this.webhookHandler.verifyWebhookHmac(queryParams);
524
+ }
525
+ handleWebhook(webhookData) {
526
+ return __awaiter(this, void 0, void 0, function* () {
527
+ return this.webhookHandler.handleWebhook(webhookData);
528
+ });
529
+ }
530
+ handleWebhookQuery(queryParams) {
531
+ return this.webhookHandler.handleWebhookQuery(queryParams);
532
+ }
404
533
  handleWebhookQueryWithItems(queryParams) {
405
534
  return __awaiter(this, void 0, void 0, function* () {
406
535
  try {
407
- // First verify the webhook
408
- const webhookResult = this.handleWebhookQuery(queryParams);
536
+ const webhookResult = this.webhookHandler.handleWebhookQuery(queryParams);
409
537
  if (!webhookResult.isValid || !webhookResult.transactionData) {
410
538
  return { isValid: false, transactionData: null };
411
539
  }
412
- // Fetch order details to get items
413
- const orderDetails = yield this.getOrderDetails(webhookResult.transactionData.orderId);
414
- // Combine webhook data with items
540
+ const orderDetails = yield this.orderManager.getOrderDetails(webhookResult.transactionData.orderId);
415
541
  const transactionData = Object.assign(Object.assign({}, webhookResult.transactionData), { items: orderDetails.items });
416
542
  return { isValid: true, transactionData };
417
543
  }
@@ -421,5 +547,20 @@ class PaymobService {
421
547
  }
422
548
  });
423
549
  }
550
+ getTransactionStatus(transactionId) {
551
+ return __awaiter(this, void 0, void 0, function* () {
552
+ return this.orderManager.getTransactionStatus(transactionId);
553
+ });
554
+ }
555
+ getOrderDetails(orderId) {
556
+ return __awaiter(this, void 0, void 0, function* () {
557
+ return this.orderManager.getOrderDetails(orderId);
558
+ });
559
+ }
560
+ getAuthToken() {
561
+ return __awaiter(this, void 0, void 0, function* () {
562
+ return this.auth.getAuthToken();
563
+ });
564
+ }
424
565
  }
425
566
  exports.PaymobService = PaymobService;
@@ -0,0 +1,222 @@
1
+ export interface PaymobMerchant {
2
+ id: number;
3
+ created_at: string;
4
+ phones: string[];
5
+ company_emails: string[];
6
+ company_name: string;
7
+ state: string;
8
+ country: string;
9
+ city: string;
10
+ postal_code: string;
11
+ street: string;
12
+ }
13
+ export interface PaymobOrderDetailsResponse {
14
+ id: number;
15
+ amount_cents: number;
16
+ currency: string;
17
+ items: PaymobOrderItem[];
18
+ created_at: string;
19
+ merchant_order_id: string;
20
+ }
21
+ export interface PaymobOrder {
22
+ id: number;
23
+ created_at: string;
24
+ delivery_needed: boolean;
25
+ merchant: PaymobMerchant;
26
+ collector: null;
27
+ amount_cents: number;
28
+ shipping_data: null;
29
+ currency: string;
30
+ is_payment_locked: boolean;
31
+ is_return: boolean;
32
+ is_cancel: boolean;
33
+ is_returned: boolean;
34
+ is_canceled: boolean;
35
+ merchant_order_id: string;
36
+ wallet_notification: null;
37
+ paid_amount_cents: number;
38
+ notify_user_with_email: boolean;
39
+ items: PaymobOrderItem[];
40
+ order_url: string;
41
+ commission_fee: number;
42
+ delivery_fee_cents: number;
43
+ delivery_voucher_cost: number;
44
+ discount: number;
45
+ metadata: Record<string, any>;
46
+ }
47
+ export interface PaymobOrderItem {
48
+ name: string;
49
+ description: string;
50
+ amount: number;
51
+ quantity: number;
52
+ }
53
+ export interface PaymobSourceData {
54
+ type: string;
55
+ pan: string;
56
+ sub_type: string;
57
+ }
58
+ export interface PaymobWebhookData {
59
+ obj: {
60
+ id: number;
61
+ amount_cents: number;
62
+ success: boolean;
63
+ is_refunded: boolean;
64
+ is_captured: boolean;
65
+ is_voided: boolean;
66
+ is_standalone_payment: boolean;
67
+ is_void: boolean;
68
+ is_refund: boolean;
69
+ is_3d_secure: boolean;
70
+ integration_id: number;
71
+ profile_id: number;
72
+ has_parent_transaction: boolean;
73
+ order: PaymobOrder;
74
+ created_at: string;
75
+ transaction_processed_callback_responses: null;
76
+ currency: string;
77
+ source_data: PaymobSourceData;
78
+ api_source: string;
79
+ terminal_id: string;
80
+ merchant_commission: number;
81
+ merchant_staff_tag: null;
82
+ hmac: string;
83
+ };
84
+ }
85
+ export interface PaymobIntentionResponse {
86
+ id: string;
87
+ amount: number;
88
+ currency: string;
89
+ payment_methods: any[];
90
+ items: PaymobOrderItem[];
91
+ billing_data: PaymobBillingData;
92
+ customer: PaymobCustomer;
93
+ extras: Record<string, any>;
94
+ client_secret: string;
95
+ status: string;
96
+ created_at: string;
97
+ }
98
+ export interface PaymobTransactionStatusResponse {
99
+ success: boolean;
100
+ status: string;
101
+ amount_cents: number;
102
+ currency: string;
103
+ }
104
+ export interface PaymobIntentionRequest {
105
+ amount: number;
106
+ currency: string;
107
+ payment_methods: (string | number)[];
108
+ items: PaymobOrderItem[];
109
+ billing_data: PaymobBillingData;
110
+ customer?: PaymobCustomer;
111
+ extras?: Record<string, any>;
112
+ merchant_order_id?: string;
113
+ }
114
+ export interface PaymobBillingData {
115
+ first_name: string;
116
+ last_name: string;
117
+ email: string;
118
+ phone_number: string;
119
+ apartment: string;
120
+ floor: string;
121
+ street: string;
122
+ building: string;
123
+ city?: string;
124
+ state: string;
125
+ country: string;
126
+ }
127
+ export interface PaymobCustomer {
128
+ first_name: string;
129
+ last_name: string;
130
+ email: string;
131
+ extras?: Record<string, any>;
132
+ }
133
+ export interface PaymobAuthResponse {
134
+ token: string;
135
+ }
136
+ export interface TransactionData {
137
+ orderId: number;
138
+ amount: number;
139
+ success: boolean;
140
+ currency: string;
141
+ transactionId: number;
142
+ createdAt: string;
143
+ isRefunded: boolean;
144
+ isCaptured: boolean;
145
+ isVoided: boolean;
146
+ metadata: Record<string, any>;
147
+ }
148
+ export interface WebhookQueryTransactionData extends TransactionData {
149
+ isAuth: boolean;
150
+ isStandalone: boolean;
151
+ is3dSecure: boolean;
152
+ sourceData: {
153
+ type: string;
154
+ pan: string;
155
+ subType: string;
156
+ };
157
+ responseCode: string;
158
+ message: string;
159
+ }
160
+ export interface WebhookQueryWithItemsTransactionData extends WebhookQueryTransactionData {
161
+ items: PaymobOrderItem[];
162
+ }
163
+ export interface WebhookResult<T> {
164
+ isValid: boolean;
165
+ transactionData: T | null;
166
+ }
167
+ export interface TransactionStatusResult {
168
+ success: boolean;
169
+ status: string;
170
+ amount: number;
171
+ currency: string;
172
+ }
173
+ export interface OrderDetailsResult {
174
+ id: number;
175
+ amount_cents: number;
176
+ currency: string;
177
+ items: PaymobOrderItem[];
178
+ created_at: string;
179
+ merchant_order_id: string;
180
+ }
181
+ export interface PaymentIntentionResult {
182
+ paymentUrl: string;
183
+ clientSecret: string;
184
+ }
185
+ export interface UserPaymentData {
186
+ firstName: string;
187
+ lastName: string;
188
+ email: string;
189
+ phone: string;
190
+ }
191
+ export interface PaymobConfigOptions {
192
+ apiKey?: string;
193
+ secretKey?: string;
194
+ publicKey?: string;
195
+ integrationId?: number;
196
+ baseUrl?: string;
197
+ hmacSecret?: string;
198
+ }
199
+ export declare class PaymobError extends Error {
200
+ readonly code?: string | undefined;
201
+ readonly details?: any;
202
+ constructor(message: string, code?: string | undefined, details?: any);
203
+ }
204
+ export declare class PaymobWebhookError extends PaymobError {
205
+ constructor(message: string, details?: any);
206
+ }
207
+ export declare class PaymobAuthError extends PaymobError {
208
+ constructor(message: string, details?: any);
209
+ }
210
+ export declare class PaymobPaymentError extends PaymobError {
211
+ constructor(message: string, details?: any);
212
+ }
213
+ export interface ValidationResult {
214
+ isValid: boolean;
215
+ errors: string[];
216
+ }
217
+ export interface PaymentValidationRules {
218
+ minAmount?: number;
219
+ maxAmount?: number;
220
+ allowedCurrencies?: string[];
221
+ requiredBillingFields?: string[];
222
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ // ===========================
3
+ // PAYMOB TYPE DEFINITIONS
4
+ // ===========================
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PaymobPaymentError = exports.PaymobAuthError = exports.PaymobWebhookError = exports.PaymobError = void 0;
7
+ // ===========================
8
+ // ERROR TYPES
9
+ // ===========================
10
+ class PaymobError extends Error {
11
+ constructor(message, code, details) {
12
+ super(message);
13
+ this.code = code;
14
+ this.details = details;
15
+ this.name = 'PaymobError';
16
+ }
17
+ }
18
+ exports.PaymobError = PaymobError;
19
+ class PaymobWebhookError extends PaymobError {
20
+ constructor(message, details) {
21
+ super(message, 'WEBHOOK_ERROR', details);
22
+ this.name = 'PaymobWebhookError';
23
+ }
24
+ }
25
+ exports.PaymobWebhookError = PaymobWebhookError;
26
+ class PaymobAuthError extends PaymobError {
27
+ constructor(message, details) {
28
+ super(message, 'AUTH_ERROR', details);
29
+ this.name = 'PaymobAuthError';
30
+ }
31
+ }
32
+ exports.PaymobAuthError = PaymobAuthError;
33
+ class PaymobPaymentError extends PaymobError {
34
+ constructor(message, details) {
35
+ super(message, 'PAYMENT_ERROR', details);
36
+ this.name = 'PaymobPaymentError';
37
+ }
38
+ }
39
+ exports.PaymobPaymentError = PaymobPaymentError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duvdu-v1/duvdu",
3
- "version": "1.1.268",
3
+ "version": "1.1.270",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [
@@ -33,6 +33,7 @@
33
33
  "express-async-errors": "^3.1.1",
34
34
  "express-session": "^1.18.0",
35
35
  "express-validator": "^7.0.1",
36
+ "ioredis": "^5.6.1",
36
37
  "jsonwebtoken": "^9.0.2",
37
38
  "mongoose": "^8.0.4",
38
39
  "multer": "^1.4.5-lts.1",