@duvdu-v1/duvdu 1.1.268 → 1.1.269
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/services/paymob.service.d.ts +10 -196
- package/build/services/paymob.service.js +383 -242
- package/build/types/paymob.types.d.ts +222 -0
- package/build/types/paymob.types.js +39 -0
- package/package.json +1 -1
|
@@ -1,207 +1,21 @@
|
|
|
1
|
-
|
|
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
|
|
146
|
-
private readonly
|
|
147
|
-
private readonly
|
|
148
|
-
private readonly
|
|
149
|
-
private readonly
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
65
|
-
* @
|
|
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
|
-
|
|
73
|
-
var _a;
|
|
93
|
+
getAuthToken() {
|
|
74
94
|
return __awaiter(this, void 0, void 0, function* () {
|
|
75
95
|
try {
|
|
76
|
-
const
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
112
|
-
* @
|
|
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
|
-
|
|
120
|
-
var _a;
|
|
111
|
+
getAuthHeaders() {
|
|
121
112
|
return __awaiter(this, void 0, void 0, function* () {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
310
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
336
|
-
|
|
337
|
-
}, {
|
|
338
|
-
|
|
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.
|
|
347
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}, {
|
|
364
|
-
|
|
365
|
-
|
|
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.
|
|
383
|
-
console.
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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;
|