@nehorai/payments-il 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cardcom/index.cjs +734 -0
- package/dist/cardcom/index.cjs.map +1 -0
- package/dist/cardcom/index.d.cts +188 -0
- package/dist/cardcom/index.d.ts +188 -0
- package/dist/cardcom/index.js +678 -0
- package/dist/cardcom/index.js.map +1 -0
- package/dist/cardcom-provider-uGEDtf_i.d.cts +56 -0
- package/dist/cardcom-provider-uGEDtf_i.d.ts +56 -0
- package/dist/factory.cjs +1418 -0
- package/dist/factory.cjs.map +1 -0
- package/dist/factory.d.cts +67 -0
- package/dist/factory.d.ts +67 -0
- package/dist/factory.js +1381 -0
- package/dist/factory.js.map +1 -0
- package/dist/hyp/index.cjs +885 -0
- package/dist/hyp/index.cjs.map +1 -0
- package/dist/hyp/index.d.cts +119 -0
- package/dist/hyp/index.d.ts +119 -0
- package/dist/hyp/index.js +841 -0
- package/dist/hyp/index.js.map +1 -0
- package/dist/hyp-types-BTLDpI0a.d.cts +235 -0
- package/dist/hyp-types-BTLDpI0a.d.ts +235 -0
- package/dist/index.cjs +1724 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1642 -0
- package/dist/index.js.map +1 -0
- package/dist/routing/index.cjs +146 -0
- package/dist/routing/index.cjs.map +1 -0
- package/dist/routing/index.d.cts +66 -0
- package/dist/routing/index.d.ts +66 -0
- package/dist/routing/index.js +113 -0
- package/dist/routing/index.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1642 @@
|
|
|
1
|
+
// src/providers/hyp/hyp-provider.ts
|
|
2
|
+
import { calculateCaptureDeadline } from "@nehorai/payments/types";
|
|
3
|
+
|
|
4
|
+
// src/providers/hyp/hyp-types.ts
|
|
5
|
+
var DEFAULT_HYP_ENDPOINTS = {
|
|
6
|
+
test: "https://cguat2.creditguard.co.il",
|
|
7
|
+
production: "https://cgpay3.creditguard.co.il"
|
|
8
|
+
};
|
|
9
|
+
var HYP_SUPPORTED_CURRENCIES = [
|
|
10
|
+
"ILS",
|
|
11
|
+
// Israeli Shekel (primary)
|
|
12
|
+
"USD",
|
|
13
|
+
// US Dollar
|
|
14
|
+
"EUR",
|
|
15
|
+
// Euro
|
|
16
|
+
"GBP"
|
|
17
|
+
// British Pound
|
|
18
|
+
];
|
|
19
|
+
var HYP_RESULT_CODE_MAP = {
|
|
20
|
+
"000": "captured",
|
|
21
|
+
// Success
|
|
22
|
+
"001": "failed",
|
|
23
|
+
// Declined
|
|
24
|
+
"002": "failed",
|
|
25
|
+
// Invalid card
|
|
26
|
+
"003": "failed",
|
|
27
|
+
// Expired card
|
|
28
|
+
"004": "failed",
|
|
29
|
+
// Insufficient funds
|
|
30
|
+
"005": "failed",
|
|
31
|
+
// Invalid CVV
|
|
32
|
+
"006": "failed",
|
|
33
|
+
// Card not permitted
|
|
34
|
+
"033": "failed",
|
|
35
|
+
// Lost/Stolen card
|
|
36
|
+
"034": "failed",
|
|
37
|
+
// Suspected fraud
|
|
38
|
+
"051": "failed",
|
|
39
|
+
// Insufficient funds
|
|
40
|
+
"054": "failed",
|
|
41
|
+
// Expired card
|
|
42
|
+
"057": "failed",
|
|
43
|
+
// Transaction not permitted
|
|
44
|
+
"100": "failed",
|
|
45
|
+
// System error
|
|
46
|
+
"200": "pending_authorization"
|
|
47
|
+
// Pending
|
|
48
|
+
};
|
|
49
|
+
var HYP_TRANSACTION_TYPES = {
|
|
50
|
+
/** Regular charge (immediate capture) */
|
|
51
|
+
DEBIT: "Debit",
|
|
52
|
+
/** Refund */
|
|
53
|
+
CREDIT: "Credit",
|
|
54
|
+
/** Authorization only (J5) */
|
|
55
|
+
DEBIT_J5: "Debit"
|
|
56
|
+
};
|
|
57
|
+
var HYP_TRANSACTION_CODES = {
|
|
58
|
+
/** Regular transaction */
|
|
59
|
+
REGULAR: "Regular",
|
|
60
|
+
/** Verify only (authorization) */
|
|
61
|
+
VERIFY: "Verify",
|
|
62
|
+
/** Force transaction */
|
|
63
|
+
FORCE: "Force"
|
|
64
|
+
};
|
|
65
|
+
var HYP_VALIDATION_MODES = {
|
|
66
|
+
/** Auto commit (immediate capture) */
|
|
67
|
+
AUTO_COMM: "AutoComm",
|
|
68
|
+
/** Transaction only (authorization, requires manual capture) */
|
|
69
|
+
TX_ONLY: "TxOnly"
|
|
70
|
+
};
|
|
71
|
+
var HYP_CREDIT_TYPES = {
|
|
72
|
+
/** Regular credit card */
|
|
73
|
+
REGULAR: "1",
|
|
74
|
+
/** Token/Saved card */
|
|
75
|
+
TOKEN: "8"
|
|
76
|
+
};
|
|
77
|
+
var HYP_ERROR_MAP = {
|
|
78
|
+
"001": "card_declined",
|
|
79
|
+
"002": "invalid_card",
|
|
80
|
+
"003": "expired_card",
|
|
81
|
+
"004": "insufficient_funds",
|
|
82
|
+
"005": "invalid_cvc",
|
|
83
|
+
"006": "card_declined",
|
|
84
|
+
"033": "card_declined",
|
|
85
|
+
"034": "card_declined",
|
|
86
|
+
"051": "insufficient_funds",
|
|
87
|
+
"054": "expired_card",
|
|
88
|
+
"057": "card_declined",
|
|
89
|
+
"100": "processing_error",
|
|
90
|
+
"200": "authentication_required"
|
|
91
|
+
};
|
|
92
|
+
function mapHypStatus(resultCode) {
|
|
93
|
+
return HYP_RESULT_CODE_MAP[resultCode] ?? null;
|
|
94
|
+
}
|
|
95
|
+
function mapHypError(resultCode) {
|
|
96
|
+
return HYP_ERROR_MAP[resultCode] ?? "unknown";
|
|
97
|
+
}
|
|
98
|
+
function isHypSupportedCurrency(currency) {
|
|
99
|
+
return HYP_SUPPORTED_CURRENCIES.includes(
|
|
100
|
+
currency
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
function isHypSuccess(resultCode) {
|
|
104
|
+
return resultCode === "000";
|
|
105
|
+
}
|
|
106
|
+
function formatHypAmount(amountMinor) {
|
|
107
|
+
return amountMinor;
|
|
108
|
+
}
|
|
109
|
+
function formatCardExpiration(month, year) {
|
|
110
|
+
const mm = month.padStart(2, "0");
|
|
111
|
+
const yy = year.slice(-2);
|
|
112
|
+
return `${mm}${yy}`;
|
|
113
|
+
}
|
|
114
|
+
function parseCardExpiration(expiration) {
|
|
115
|
+
const mm = expiration.substring(0, 2);
|
|
116
|
+
const yy = expiration.substring(2, 4);
|
|
117
|
+
return {
|
|
118
|
+
month: mm,
|
|
119
|
+
year: `20${yy}`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/providers/hyp/hyp-provider.ts
|
|
124
|
+
var HypProvider = class {
|
|
125
|
+
name = "hyp";
|
|
126
|
+
supportedCurrencies = HYP_SUPPORTED_CURRENCIES;
|
|
127
|
+
supportsRecurring = true;
|
|
128
|
+
supportsSplitPayments = false;
|
|
129
|
+
config;
|
|
130
|
+
constructor(config) {
|
|
131
|
+
if (!config.terminalNumber || !config.user || !config.password) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
"HypProvider requires terminalNumber, user, and password in config"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const baseUrl = config.baseUrl ?? (config.environment === "production" ? DEFAULT_HYP_ENDPOINTS.production : DEFAULT_HYP_ENDPOINTS.test);
|
|
137
|
+
this.config = { ...config, baseUrl };
|
|
138
|
+
}
|
|
139
|
+
// ==========================================================================
|
|
140
|
+
// Payment Intent Operations
|
|
141
|
+
// ==========================================================================
|
|
142
|
+
/**
|
|
143
|
+
* Create a payment intent
|
|
144
|
+
*
|
|
145
|
+
* For Hyp, this generates a hosted payment page or prepares for direct charge.
|
|
146
|
+
*/
|
|
147
|
+
async createPaymentIntent(params) {
|
|
148
|
+
try {
|
|
149
|
+
if (!params.paymentMethodId) {
|
|
150
|
+
return await this.createHostedPage(params);
|
|
151
|
+
}
|
|
152
|
+
return await this.chargeWithToken(params);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
return this.handleError(error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Create hosted payment page
|
|
159
|
+
*/
|
|
160
|
+
async createHostedPage(params) {
|
|
161
|
+
const uniqueid = params.idempotencyKey;
|
|
162
|
+
const request = {
|
|
163
|
+
terminalNumber: this.config.terminalNumber,
|
|
164
|
+
user: this.config.user,
|
|
165
|
+
password: this.config.password,
|
|
166
|
+
total: formatHypAmount(params.amount.amountMinor),
|
|
167
|
+
currency: params.amount.currency,
|
|
168
|
+
transactionType: HYP_TRANSACTION_TYPES.DEBIT,
|
|
169
|
+
transactionCode: params.captureMethod === "manual" ? HYP_TRANSACTION_CODES.VERIFY : HYP_TRANSACTION_CODES.REGULAR,
|
|
170
|
+
validation: params.captureMethod === "manual" ? HYP_VALIDATION_MODES.TX_ONLY : HYP_VALIDATION_MODES.AUTO_COMM,
|
|
171
|
+
uniqueid,
|
|
172
|
+
successUrl: params.returnUrl,
|
|
173
|
+
errorUrl: params.returnUrl,
|
|
174
|
+
cancelUrl: params.returnUrl,
|
|
175
|
+
language: "en"
|
|
176
|
+
};
|
|
177
|
+
const response = await this.sendDoDealRequest(request);
|
|
178
|
+
if (!isHypSuccess(response.resultCode)) {
|
|
179
|
+
return {
|
|
180
|
+
success: false,
|
|
181
|
+
error: response.resultDescription ?? "Transaction failed",
|
|
182
|
+
errorCode: mapHypError(response.resultCode)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
providerIntentId: response.transactionId ?? uniqueid,
|
|
188
|
+
redirectUrl: response.redirectUrl,
|
|
189
|
+
status: "created"
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Charge with saved payment method token
|
|
194
|
+
*/
|
|
195
|
+
async chargeWithToken(params) {
|
|
196
|
+
const uniqueid = params.idempotencyKey;
|
|
197
|
+
const request = {
|
|
198
|
+
terminalNumber: this.config.terminalNumber,
|
|
199
|
+
user: this.config.user,
|
|
200
|
+
password: this.config.password,
|
|
201
|
+
total: formatHypAmount(params.amount.amountMinor),
|
|
202
|
+
currency: params.amount.currency,
|
|
203
|
+
transactionType: HYP_TRANSACTION_TYPES.DEBIT,
|
|
204
|
+
transactionCode: params.captureMethod === "manual" ? HYP_TRANSACTION_CODES.VERIFY : HYP_TRANSACTION_CODES.REGULAR,
|
|
205
|
+
validation: params.captureMethod === "manual" ? HYP_VALIDATION_MODES.TX_ONLY : HYP_VALIDATION_MODES.AUTO_COMM,
|
|
206
|
+
creditType: HYP_CREDIT_TYPES.TOKEN,
|
|
207
|
+
cardToken: params.paymentMethodId,
|
|
208
|
+
uniqueid
|
|
209
|
+
};
|
|
210
|
+
const response = await this.sendDoDealRequest(request);
|
|
211
|
+
if (!isHypSuccess(response.resultCode)) {
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
error: response.resultDescription ?? "Transaction failed",
|
|
215
|
+
errorCode: mapHypError(response.resultCode)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const status = mapHypStatus(response.resultCode) ?? "created";
|
|
219
|
+
return {
|
|
220
|
+
success: true,
|
|
221
|
+
providerIntentId: response.transactionId ?? uniqueid,
|
|
222
|
+
status
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
async authorize(params) {
|
|
226
|
+
try {
|
|
227
|
+
return {
|
|
228
|
+
success: true,
|
|
229
|
+
authorizationCode: params.providerIntentId,
|
|
230
|
+
status: "authorized",
|
|
231
|
+
captureDeadline: calculateCaptureDeadline(/* @__PURE__ */ new Date())
|
|
232
|
+
};
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return this.handleError(error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async capture(params) {
|
|
238
|
+
try {
|
|
239
|
+
const request = {
|
|
240
|
+
terminalNumber: this.config.terminalNumber,
|
|
241
|
+
user: this.config.user,
|
|
242
|
+
password: this.config.password,
|
|
243
|
+
total: params.amount ? formatHypAmount(params.amount.amountMinor) : void 0,
|
|
244
|
+
currency: params.amount?.currency ?? "ILS",
|
|
245
|
+
transactionType: HYP_TRANSACTION_TYPES.DEBIT,
|
|
246
|
+
transactionCode: HYP_TRANSACTION_CODES.FORCE,
|
|
247
|
+
validation: HYP_VALIDATION_MODES.AUTO_COMM,
|
|
248
|
+
uniqueid: params.idempotencyKey,
|
|
249
|
+
authorizationCode: params.providerIntentId
|
|
250
|
+
};
|
|
251
|
+
const response = await this.sendDoDealRequest(request);
|
|
252
|
+
if (!isHypSuccess(response.resultCode)) {
|
|
253
|
+
return {
|
|
254
|
+
success: false,
|
|
255
|
+
error: response.resultDescription ?? "Capture failed",
|
|
256
|
+
errorCode: mapHypError(response.resultCode)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
success: true,
|
|
261
|
+
providerTransactionId: response.transactionId ?? params.providerIntentId,
|
|
262
|
+
status: "captured",
|
|
263
|
+
capturedAmount: params.amount ?? {
|
|
264
|
+
amountMinor: 0,
|
|
265
|
+
currency: "ILS"
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
return this.handleError(error);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async void(params) {
|
|
273
|
+
try {
|
|
274
|
+
const request = {
|
|
275
|
+
terminalNumber: this.config.terminalNumber,
|
|
276
|
+
user: this.config.user,
|
|
277
|
+
password: this.config.password,
|
|
278
|
+
transactionId: params.providerIntentId,
|
|
279
|
+
currency: "ILS",
|
|
280
|
+
uniqueid: params.providerIntentId
|
|
281
|
+
};
|
|
282
|
+
const response = await this.sendRefundRequest(request);
|
|
283
|
+
if (!isHypSuccess(response.resultCode)) {
|
|
284
|
+
return {
|
|
285
|
+
success: false,
|
|
286
|
+
error: response.resultDescription ?? "Void failed"
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return { success: true, status: "voided" };
|
|
290
|
+
} catch (error) {
|
|
291
|
+
return this.handleError(error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ==========================================================================
|
|
295
|
+
// Refunds
|
|
296
|
+
// ==========================================================================
|
|
297
|
+
async refund(params) {
|
|
298
|
+
try {
|
|
299
|
+
const request = {
|
|
300
|
+
terminalNumber: this.config.terminalNumber,
|
|
301
|
+
user: this.config.user,
|
|
302
|
+
password: this.config.password,
|
|
303
|
+
transactionId: params.providerTransactionId,
|
|
304
|
+
total: params.amount ? formatHypAmount(params.amount.amountMinor) : void 0,
|
|
305
|
+
currency: params.amount?.currency ?? "ILS",
|
|
306
|
+
uniqueid: params.idempotencyKey
|
|
307
|
+
};
|
|
308
|
+
const response = await this.sendRefundRequest(request);
|
|
309
|
+
if (!isHypSuccess(response.resultCode)) {
|
|
310
|
+
return {
|
|
311
|
+
success: false,
|
|
312
|
+
error: response.resultDescription ?? "Refund failed"
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
success: true,
|
|
317
|
+
providerRefundId: response.transactionId ?? params.idempotencyKey,
|
|
318
|
+
refundedAmount: params.amount ?? {
|
|
319
|
+
amountMinor: 0,
|
|
320
|
+
currency: "ILS"
|
|
321
|
+
},
|
|
322
|
+
status: "succeeded"
|
|
323
|
+
};
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return this.handleError(error);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// ==========================================================================
|
|
329
|
+
// Payment Methods (Tokenization)
|
|
330
|
+
// ==========================================================================
|
|
331
|
+
async createSetupIntent(params) {
|
|
332
|
+
try {
|
|
333
|
+
const uniqueid = `setup_${params.userId}_${Date.now()}`;
|
|
334
|
+
const request = {
|
|
335
|
+
terminalNumber: this.config.terminalNumber,
|
|
336
|
+
user: this.config.user,
|
|
337
|
+
password: this.config.password,
|
|
338
|
+
total: 0,
|
|
339
|
+
currency: "ILS",
|
|
340
|
+
transactionType: HYP_TRANSACTION_TYPES.DEBIT,
|
|
341
|
+
transactionCode: HYP_TRANSACTION_CODES.VERIFY,
|
|
342
|
+
validation: HYP_VALIDATION_MODES.TX_ONLY,
|
|
343
|
+
creditType: HYP_CREDIT_TYPES.TOKEN,
|
|
344
|
+
customerData: params.customerId ?? params.userId,
|
|
345
|
+
uniqueid,
|
|
346
|
+
language: "en"
|
|
347
|
+
};
|
|
348
|
+
const response = await this.sendDoDealRequest(request);
|
|
349
|
+
if (!isHypSuccess(response.resultCode)) {
|
|
350
|
+
return {
|
|
351
|
+
success: false,
|
|
352
|
+
error: response.resultDescription ?? "Setup failed"
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
success: true,
|
|
357
|
+
setupIntentId: response.transactionId ?? uniqueid,
|
|
358
|
+
clientSecret: response.redirectUrl
|
|
359
|
+
};
|
|
360
|
+
} catch (error) {
|
|
361
|
+
return this.handleError(error);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async savePaymentMethod(params) {
|
|
365
|
+
try {
|
|
366
|
+
const cardToken = params.setupData.cardToken;
|
|
367
|
+
const cardMask = params.setupData.cardMask;
|
|
368
|
+
const cardBrand = params.setupData.cardBrand;
|
|
369
|
+
const cardExpiration = params.setupData.cardExpiration;
|
|
370
|
+
if (!cardToken) {
|
|
371
|
+
return {
|
|
372
|
+
success: false,
|
|
373
|
+
error: "No card token received"
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
success: true,
|
|
378
|
+
paymentMethodId: cardToken,
|
|
379
|
+
cardBrand: cardBrand ?? "unknown",
|
|
380
|
+
cardLast4: cardMask?.slice(-4) ?? "0000",
|
|
381
|
+
cardExpMonth: cardExpiration?.substring(0, 2) ?? "01",
|
|
382
|
+
cardExpYear: `20${cardExpiration?.substring(2, 4) ?? "99"}`
|
|
383
|
+
};
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return this.handleError(error);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
async deletePaymentMethod(_paymentMethodId) {
|
|
389
|
+
return {
|
|
390
|
+
success: true
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
// ==========================================================================
|
|
394
|
+
// Customer Management
|
|
395
|
+
// ==========================================================================
|
|
396
|
+
async createCustomer(params) {
|
|
397
|
+
return {
|
|
398
|
+
success: true,
|
|
399
|
+
customerId: params.userId
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
async getOrCreateCustomer(userId, _email) {
|
|
403
|
+
return {
|
|
404
|
+
success: true,
|
|
405
|
+
customerId: userId
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
// ==========================================================================
|
|
409
|
+
// Health & Security
|
|
410
|
+
// ==========================================================================
|
|
411
|
+
async getHealth() {
|
|
412
|
+
const start = Date.now();
|
|
413
|
+
try {
|
|
414
|
+
const response = await fetch(`${this.config.baseUrl}/xpo/Relay`, {
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: {
|
|
417
|
+
"Content-Type": "text/xml"
|
|
418
|
+
},
|
|
419
|
+
body: this.buildTestXML(),
|
|
420
|
+
signal: AbortSignal.timeout(5e3)
|
|
421
|
+
});
|
|
422
|
+
const healthy = response.ok;
|
|
423
|
+
return {
|
|
424
|
+
provider: "hyp",
|
|
425
|
+
healthy,
|
|
426
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
427
|
+
avgLatencyMs: Date.now() - start,
|
|
428
|
+
circuitBreakerOpen: false
|
|
429
|
+
};
|
|
430
|
+
} catch {
|
|
431
|
+
return {
|
|
432
|
+
provider: "hyp",
|
|
433
|
+
healthy: false,
|
|
434
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
435
|
+
circuitBreakerOpen: false
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
validateWebhookSignature(_payload, _signature) {
|
|
440
|
+
if (!this.config.webhookSecret) return false;
|
|
441
|
+
return !!this.config.webhookSecret;
|
|
442
|
+
}
|
|
443
|
+
async getPaymentIntentStatus(_providerIntentId) {
|
|
444
|
+
return {
|
|
445
|
+
status: "unknown",
|
|
446
|
+
error: "Status query not supported by Hyp basic integration"
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
// ==========================================================================
|
|
450
|
+
// XML Request Builders
|
|
451
|
+
// ==========================================================================
|
|
452
|
+
buildDoDealXML(request) {
|
|
453
|
+
const parts = [];
|
|
454
|
+
parts.push('<?xml version="1.0" encoding="utf-8"?>');
|
|
455
|
+
parts.push("<ashrait>");
|
|
456
|
+
parts.push("<request>");
|
|
457
|
+
parts.push(`<version>1000</version>`);
|
|
458
|
+
parts.push("<language>ENG</language>");
|
|
459
|
+
parts.push("<command>doDeal</command>");
|
|
460
|
+
parts.push(`<terminalNumber>${this.escapeXml(request.terminalNumber)}</terminalNumber>`);
|
|
461
|
+
parts.push(`<user>${this.escapeXml(request.user)}</user>`);
|
|
462
|
+
parts.push(`<password>${this.escapeXml(request.password)}</password>`);
|
|
463
|
+
if (request.cardNo) {
|
|
464
|
+
parts.push(`<cardNo>${this.escapeXml(request.cardNo)}</cardNo>`);
|
|
465
|
+
}
|
|
466
|
+
if (request.cardExpiration) {
|
|
467
|
+
parts.push(`<cardExpiration>${this.escapeXml(request.cardExpiration)}</cardExpiration>`);
|
|
468
|
+
}
|
|
469
|
+
if (request.cvv) {
|
|
470
|
+
parts.push(`<cvv>${this.escapeXml(request.cvv)}</cvv>`);
|
|
471
|
+
}
|
|
472
|
+
if (request.cardToken) {
|
|
473
|
+
parts.push(`<cardToken>${this.escapeXml(request.cardToken)}</cardToken>`);
|
|
474
|
+
}
|
|
475
|
+
if (request.authorizationCode) {
|
|
476
|
+
parts.push(`<authNumber>${this.escapeXml(request.authorizationCode)}</authNumber>`);
|
|
477
|
+
}
|
|
478
|
+
if (request.total !== void 0) {
|
|
479
|
+
parts.push(`<total>${request.total}</total>`);
|
|
480
|
+
}
|
|
481
|
+
parts.push(`<currency>${this.escapeXml(request.currency)}</currency>`);
|
|
482
|
+
parts.push(`<transactionType>${this.escapeXml(request.transactionType)}</transactionType>`);
|
|
483
|
+
if (request.transactionCode) {
|
|
484
|
+
parts.push(`<transactionCode>${this.escapeXml(request.transactionCode)}</transactionCode>`);
|
|
485
|
+
}
|
|
486
|
+
if (request.creditType) {
|
|
487
|
+
parts.push(`<creditType>${request.creditType}</creditType>`);
|
|
488
|
+
}
|
|
489
|
+
if (request.validation) {
|
|
490
|
+
parts.push(`<validation>${this.escapeXml(request.validation)}</validation>`);
|
|
491
|
+
}
|
|
492
|
+
if (request.uniqueid) {
|
|
493
|
+
parts.push(`<uniqueid>${this.escapeXml(request.uniqueid)}</uniqueid>`);
|
|
494
|
+
}
|
|
495
|
+
if (request.customerData) {
|
|
496
|
+
parts.push(`<customerData>${this.escapeXml(request.customerData)}</customerData>`);
|
|
497
|
+
}
|
|
498
|
+
if (request.successUrl) {
|
|
499
|
+
parts.push(`<successUrl>${this.escapeXml(request.successUrl)}</successUrl>`);
|
|
500
|
+
}
|
|
501
|
+
if (request.errorUrl) {
|
|
502
|
+
parts.push(`<errorUrl>${this.escapeXml(request.errorUrl)}</errorUrl>`);
|
|
503
|
+
}
|
|
504
|
+
if (request.cancelUrl) {
|
|
505
|
+
parts.push(`<cancelUrl>${this.escapeXml(request.cancelUrl)}</cancelUrl>`);
|
|
506
|
+
}
|
|
507
|
+
parts.push("</request>");
|
|
508
|
+
parts.push("</ashrait>");
|
|
509
|
+
return parts.join("");
|
|
510
|
+
}
|
|
511
|
+
buildRefundXML(request) {
|
|
512
|
+
const parts = [];
|
|
513
|
+
parts.push('<?xml version="1.0" encoding="utf-8"?>');
|
|
514
|
+
parts.push("<ashrait>");
|
|
515
|
+
parts.push("<request>");
|
|
516
|
+
parts.push(`<version>1000</version>`);
|
|
517
|
+
parts.push("<language>ENG</language>");
|
|
518
|
+
parts.push("<command>refundDeal</command>");
|
|
519
|
+
parts.push(`<terminalNumber>${this.escapeXml(request.terminalNumber)}</terminalNumber>`);
|
|
520
|
+
parts.push(`<user>${this.escapeXml(request.user)}</user>`);
|
|
521
|
+
parts.push(`<password>${this.escapeXml(request.password)}</password>`);
|
|
522
|
+
parts.push(`<transactionId>${this.escapeXml(request.transactionId)}</transactionId>`);
|
|
523
|
+
parts.push(`<currency>${this.escapeXml(request.currency)}</currency>`);
|
|
524
|
+
if (request.total !== void 0) {
|
|
525
|
+
parts.push(`<total>${request.total}</total>`);
|
|
526
|
+
}
|
|
527
|
+
if (request.uniqueid) {
|
|
528
|
+
parts.push(`<uniqueid>${this.escapeXml(request.uniqueid)}</uniqueid>`);
|
|
529
|
+
}
|
|
530
|
+
parts.push("</request>");
|
|
531
|
+
parts.push("</ashrait>");
|
|
532
|
+
return parts.join("");
|
|
533
|
+
}
|
|
534
|
+
buildTestXML() {
|
|
535
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
536
|
+
<ashrait>
|
|
537
|
+
<request>
|
|
538
|
+
<version>1000</version>
|
|
539
|
+
<language>ENG</language>
|
|
540
|
+
<command>echo</command>
|
|
541
|
+
</request>
|
|
542
|
+
</ashrait>`;
|
|
543
|
+
}
|
|
544
|
+
// ==========================================================================
|
|
545
|
+
// HTTP Helpers
|
|
546
|
+
// ==========================================================================
|
|
547
|
+
async sendDoDealRequest(request) {
|
|
548
|
+
const xmlBody = this.buildDoDealXML(request);
|
|
549
|
+
const response = await fetch(`${this.config.baseUrl}/xpo/Relay`, {
|
|
550
|
+
method: "POST",
|
|
551
|
+
headers: {
|
|
552
|
+
"Content-Type": "text/xml; charset=utf-8"
|
|
553
|
+
},
|
|
554
|
+
body: xmlBody
|
|
555
|
+
});
|
|
556
|
+
if (!response.ok) {
|
|
557
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
558
|
+
}
|
|
559
|
+
const xmlResponse = await response.text();
|
|
560
|
+
return this.parseDoDealResponse(xmlResponse);
|
|
561
|
+
}
|
|
562
|
+
async sendRefundRequest(request) {
|
|
563
|
+
const xmlBody = this.buildRefundXML(request);
|
|
564
|
+
const response = await fetch(`${this.config.baseUrl}/xpo/Relay`, {
|
|
565
|
+
method: "POST",
|
|
566
|
+
headers: {
|
|
567
|
+
"Content-Type": "text/xml; charset=utf-8"
|
|
568
|
+
},
|
|
569
|
+
body: xmlBody
|
|
570
|
+
});
|
|
571
|
+
if (!response.ok) {
|
|
572
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
573
|
+
}
|
|
574
|
+
const xmlResponse = await response.text();
|
|
575
|
+
return this.parseRefundResponse(xmlResponse);
|
|
576
|
+
}
|
|
577
|
+
// ==========================================================================
|
|
578
|
+
// XML Parsing
|
|
579
|
+
// ==========================================================================
|
|
580
|
+
parseDoDealResponse(xml) {
|
|
581
|
+
return {
|
|
582
|
+
resultCode: this.extractXmlValue(xml, "resultCode") ?? "100",
|
|
583
|
+
resultDescription: this.extractXmlValue(xml, "resultDescription"),
|
|
584
|
+
transactionId: this.extractXmlValue(xml, "transactionId"),
|
|
585
|
+
authorizationCode: this.extractXmlValue(xml, "authorizationCode"),
|
|
586
|
+
voucherNumber: this.extractXmlValue(xml, "voucherNumber"),
|
|
587
|
+
cardToken: this.extractXmlValue(xml, "cardToken"),
|
|
588
|
+
cardMask: this.extractXmlValue(xml, "cardMask"),
|
|
589
|
+
cardBrand: this.extractXmlValue(xml, "cardBrand"),
|
|
590
|
+
cardExpiration: this.extractXmlValue(xml, "cardExpiration"),
|
|
591
|
+
redirectUrl: this.extractXmlValue(xml, "redirectUrl"),
|
|
592
|
+
uniqueid: this.extractXmlValue(xml, "uniqueid"),
|
|
593
|
+
rawXml: xml
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
parseRefundResponse(xml) {
|
|
597
|
+
return {
|
|
598
|
+
resultCode: this.extractXmlValue(xml, "resultCode") ?? "100",
|
|
599
|
+
resultDescription: this.extractXmlValue(xml, "resultDescription"),
|
|
600
|
+
transactionId: this.extractXmlValue(xml, "transactionId"),
|
|
601
|
+
authorizationCode: this.extractXmlValue(xml, "authorizationCode"),
|
|
602
|
+
uniqueid: this.extractXmlValue(xml, "uniqueid")
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
extractXmlValue(xml, tagName) {
|
|
606
|
+
const regex = new RegExp(`<${tagName}>([^<]*)</${tagName}>`, "i");
|
|
607
|
+
const match = xml.match(regex);
|
|
608
|
+
return match ? match[1].trim() : void 0;
|
|
609
|
+
}
|
|
610
|
+
escapeXml(str) {
|
|
611
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
612
|
+
}
|
|
613
|
+
// ==========================================================================
|
|
614
|
+
// Error Handling
|
|
615
|
+
// ==========================================================================
|
|
616
|
+
handleError(error) {
|
|
617
|
+
if (error instanceof Error) {
|
|
618
|
+
return {
|
|
619
|
+
success: false,
|
|
620
|
+
error: error.message,
|
|
621
|
+
errorCode: "unknown"
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
success: false,
|
|
626
|
+
error: "Unknown error occurred",
|
|
627
|
+
errorCode: "unknown"
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// src/providers/hyp/hyp-webhook-handler.ts
|
|
633
|
+
var HYP_EVENT_TYPES = {
|
|
634
|
+
TRANSACTION_SUCCESS: "transaction.success",
|
|
635
|
+
TRANSACTION_FAILED: "transaction.failed",
|
|
636
|
+
TRANSACTION_PENDING: "transaction.pending",
|
|
637
|
+
REFUND_SUCCESS: "refund.success",
|
|
638
|
+
REFUND_FAILED: "refund.failed"
|
|
639
|
+
};
|
|
640
|
+
var HypWebhookHandler = class {
|
|
641
|
+
provider = "hyp";
|
|
642
|
+
supportedEventTypes = Object.values(HYP_EVENT_TYPES);
|
|
643
|
+
parseEvent(rawPayload) {
|
|
644
|
+
try {
|
|
645
|
+
const resultCode = String(rawPayload.resultCode ?? "100");
|
|
646
|
+
const resultDescription = String(rawPayload.resultDescription ?? "");
|
|
647
|
+
const transactionId = String(rawPayload.transactionId ?? "");
|
|
648
|
+
const uniqueid = String(rawPayload.uniqueid ?? "");
|
|
649
|
+
const total = Number(rawPayload.total ?? 0);
|
|
650
|
+
const currency = String(rawPayload.currency ?? "ILS");
|
|
651
|
+
const eventType = this.determineEventType(resultCode);
|
|
652
|
+
const newStatus = mapHypStatus(resultCode);
|
|
653
|
+
const event = {
|
|
654
|
+
provider: "hyp",
|
|
655
|
+
eventId: uniqueid || transactionId || `hyp_${Date.now()}`,
|
|
656
|
+
eventType,
|
|
657
|
+
providerTransactionId: transactionId,
|
|
658
|
+
amountMinor: total,
|
|
659
|
+
currency,
|
|
660
|
+
newStatus: newStatus ?? void 0,
|
|
661
|
+
error: isHypSuccess(resultCode) ? void 0 : {
|
|
662
|
+
code: resultCode,
|
|
663
|
+
message: resultDescription
|
|
664
|
+
},
|
|
665
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
666
|
+
rawPayload
|
|
667
|
+
};
|
|
668
|
+
return {
|
|
669
|
+
success: true,
|
|
670
|
+
event
|
|
671
|
+
};
|
|
672
|
+
} catch (error) {
|
|
673
|
+
return {
|
|
674
|
+
success: false,
|
|
675
|
+
error: error instanceof Error ? error.message : "Failed to parse webhook"
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async processEvent(event) {
|
|
680
|
+
try {
|
|
681
|
+
if (!event.providerTransactionId) {
|
|
682
|
+
return {
|
|
683
|
+
success: false,
|
|
684
|
+
error: "Missing transaction ID in webhook",
|
|
685
|
+
action: "ignored_event_type"
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
const action = this.determineAction(event);
|
|
689
|
+
return {
|
|
690
|
+
success: true,
|
|
691
|
+
transactionId: event.providerTransactionId,
|
|
692
|
+
action
|
|
693
|
+
};
|
|
694
|
+
} catch (error) {
|
|
695
|
+
return {
|
|
696
|
+
success: false,
|
|
697
|
+
error: error instanceof Error ? error.message : "Processing failed",
|
|
698
|
+
action: "ignored_event_type"
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
canHandle(eventType) {
|
|
703
|
+
return this.supportedEventTypes.includes(
|
|
704
|
+
eventType
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
async reconcile(_transactionId, _providerTransactionId) {
|
|
708
|
+
return {
|
|
709
|
+
reconciled: false,
|
|
710
|
+
finalStatus: "pending_authorization",
|
|
711
|
+
source: "webhook",
|
|
712
|
+
statusChanged: false
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
mapEventType(providerEventType) {
|
|
716
|
+
return providerEventType;
|
|
717
|
+
}
|
|
718
|
+
mapStatus(providerStatus) {
|
|
719
|
+
return mapHypStatus(providerStatus);
|
|
720
|
+
}
|
|
721
|
+
// ==========================================================================
|
|
722
|
+
// Helper Methods
|
|
723
|
+
// ==========================================================================
|
|
724
|
+
determineEventType(resultCode) {
|
|
725
|
+
if (isHypSuccess(resultCode)) {
|
|
726
|
+
return HYP_EVENT_TYPES.TRANSACTION_SUCCESS;
|
|
727
|
+
}
|
|
728
|
+
if (resultCode === "200") {
|
|
729
|
+
return HYP_EVENT_TYPES.TRANSACTION_PENDING;
|
|
730
|
+
}
|
|
731
|
+
return HYP_EVENT_TYPES.TRANSACTION_FAILED;
|
|
732
|
+
}
|
|
733
|
+
determineAction(event) {
|
|
734
|
+
switch (event.eventType) {
|
|
735
|
+
case HYP_EVENT_TYPES.TRANSACTION_SUCCESS:
|
|
736
|
+
case HYP_EVENT_TYPES.TRANSACTION_FAILED:
|
|
737
|
+
case HYP_EVENT_TYPES.TRANSACTION_PENDING:
|
|
738
|
+
case HYP_EVENT_TYPES.REFUND_FAILED:
|
|
739
|
+
return "status_updated";
|
|
740
|
+
case HYP_EVENT_TYPES.REFUND_SUCCESS:
|
|
741
|
+
return "refund_processed";
|
|
742
|
+
default:
|
|
743
|
+
return "ignored_event_type";
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
// ==========================================================================
|
|
747
|
+
// Webhook Validation
|
|
748
|
+
// ==========================================================================
|
|
749
|
+
validateSignature(payload, signature, secret) {
|
|
750
|
+
const hasRequiredParams = payload.resultCode !== void 0 && (payload.transactionId !== void 0 || payload.uniqueid !== void 0);
|
|
751
|
+
if (!hasRequiredParams) {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
if (secret && signature) {
|
|
755
|
+
return this.validateHMAC(payload, signature, secret);
|
|
756
|
+
}
|
|
757
|
+
return hasRequiredParams;
|
|
758
|
+
}
|
|
759
|
+
validateHMAC(_payload, signature, _secret) {
|
|
760
|
+
try {
|
|
761
|
+
return !!signature;
|
|
762
|
+
} catch {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
// ==========================================================================
|
|
767
|
+
// Callback URL Builders
|
|
768
|
+
// ==========================================================================
|
|
769
|
+
buildSuccessUrl(baseUrl, transactionId) {
|
|
770
|
+
return `${baseUrl}/api/payments/hyp/callback?status=success&txId=${transactionId}`;
|
|
771
|
+
}
|
|
772
|
+
buildErrorUrl(baseUrl, transactionId) {
|
|
773
|
+
return `${baseUrl}/api/payments/hyp/callback?status=error&txId=${transactionId}`;
|
|
774
|
+
}
|
|
775
|
+
buildCancelUrl(baseUrl, transactionId) {
|
|
776
|
+
return `${baseUrl}/api/payments/hyp/callback?status=cancel&txId=${transactionId}`;
|
|
777
|
+
}
|
|
778
|
+
// ==========================================================================
|
|
779
|
+
// Response Parsing
|
|
780
|
+
// ==========================================================================
|
|
781
|
+
extractErrorDetails(payload) {
|
|
782
|
+
const resultCode = String(payload.resultCode ?? "unknown");
|
|
783
|
+
const resultDescription = String(payload.resultDescription ?? "Unknown error");
|
|
784
|
+
return {
|
|
785
|
+
code: resultCode,
|
|
786
|
+
message: resultDescription,
|
|
787
|
+
userMessage: this.getUserFriendlyMessage(resultCode)
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
getUserFriendlyMessage(resultCode) {
|
|
791
|
+
const errorCode = mapHypError(resultCode);
|
|
792
|
+
const messages = {
|
|
793
|
+
card_declined: "Your card was declined. Please try another payment method.",
|
|
794
|
+
invalid_card: "The card information is invalid. Please check and try again.",
|
|
795
|
+
expired_card: "Your card has expired. Please use a different card.",
|
|
796
|
+
insufficient_funds: "Insufficient funds. Please try another payment method.",
|
|
797
|
+
invalid_cvc: "The security code (CVV) is incorrect.",
|
|
798
|
+
processing_error: "A processing error occurred. Please try again.",
|
|
799
|
+
authentication_required: "Additional authentication is required. Please complete the verification.",
|
|
800
|
+
unknown: "An error occurred. Please try again or contact support."
|
|
801
|
+
};
|
|
802
|
+
return messages[errorCode] ?? messages.unknown;
|
|
803
|
+
}
|
|
804
|
+
extractCardDetails(payload) {
|
|
805
|
+
const cardToken = payload.cardToken;
|
|
806
|
+
const cardMask = payload.cardMask;
|
|
807
|
+
const cardBrand = payload.cardBrand;
|
|
808
|
+
const cardExpiration = payload.cardExpiration;
|
|
809
|
+
return {
|
|
810
|
+
cardToken,
|
|
811
|
+
cardMask,
|
|
812
|
+
cardBrand,
|
|
813
|
+
cardExpiration,
|
|
814
|
+
last4: cardMask?.slice(-4)
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
function createHypWebhookHandler() {
|
|
819
|
+
return new HypWebhookHandler();
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/providers/cardcom/cardcom-provider.ts
|
|
823
|
+
import * as crypto from "crypto";
|
|
824
|
+
import { calculateCaptureDeadline as calculateCaptureDeadline2 } from "@nehorai/payments/types";
|
|
825
|
+
|
|
826
|
+
// src/providers/cardcom/cardcom-types.ts
|
|
827
|
+
var CARDCOM_API_BASE = "https://secure.cardcom.solutions";
|
|
828
|
+
var CARDCOM_ENDPOINTS = {
|
|
829
|
+
LOW_PROFILE_CREATE: "/api/v11/LowProfile/Create",
|
|
830
|
+
LOW_PROFILE_STATUS: "/Interface/BillGoldGetLowProfileIndicator.aspx",
|
|
831
|
+
DIRECT_CHARGE: "/api/v11/Transactions/Transaction",
|
|
832
|
+
REFUND: "/api/v11/Transactions/RefundByTransactionId"
|
|
833
|
+
};
|
|
834
|
+
var CARDCOM_SUPPORTED_CURRENCIES = ["ILS", "USD", "EUR", "GBP"];
|
|
835
|
+
var CardcomOperation = /* @__PURE__ */ ((CardcomOperation2) => {
|
|
836
|
+
CardcomOperation2[CardcomOperation2["BILL_ONLY"] = 1] = "BILL_ONLY";
|
|
837
|
+
CardcomOperation2[CardcomOperation2["BILL_AND_CREATE_TOKEN"] = 2] = "BILL_AND_CREATE_TOKEN";
|
|
838
|
+
CardcomOperation2[CardcomOperation2["CREATE_TOKEN_ONLY"] = 3] = "CREATE_TOKEN_ONLY";
|
|
839
|
+
CardcomOperation2[CardcomOperation2["SUSPEND_DEAL_ONLY"] = 4] = "SUSPEND_DEAL_ONLY";
|
|
840
|
+
return CardcomOperation2;
|
|
841
|
+
})(CardcomOperation || {});
|
|
842
|
+
var CardcomTransactionType = /* @__PURE__ */ ((CardcomTransactionType2) => {
|
|
843
|
+
CardcomTransactionType2[CardcomTransactionType2["REGULAR"] = 1] = "REGULAR";
|
|
844
|
+
CardcomTransactionType2[CardcomTransactionType2["CREDIT"] = 2] = "CREDIT";
|
|
845
|
+
CardcomTransactionType2[CardcomTransactionType2["INSTALLMENTS"] = 3] = "INSTALLMENTS";
|
|
846
|
+
return CardcomTransactionType2;
|
|
847
|
+
})(CardcomTransactionType || {});
|
|
848
|
+
var CARDCOM_WEBHOOK_EVENTS = [
|
|
849
|
+
"payment.completed",
|
|
850
|
+
"payment.declined",
|
|
851
|
+
"payment.authorized"
|
|
852
|
+
];
|
|
853
|
+
var CARDCOM_DEAL_RESPONSE_ACTIONS = {
|
|
854
|
+
0: "pending",
|
|
855
|
+
1: "approved",
|
|
856
|
+
2: "declined",
|
|
857
|
+
3: "error"
|
|
858
|
+
};
|
|
859
|
+
var CARDCOM_RESPONSE_CODE_MAP = {
|
|
860
|
+
0: "created",
|
|
861
|
+
1: "failed",
|
|
862
|
+
2: "failed",
|
|
863
|
+
3: "failed",
|
|
864
|
+
4: "failed",
|
|
865
|
+
5: "failed",
|
|
866
|
+
6: "failed",
|
|
867
|
+
7: "failed",
|
|
868
|
+
8: "failed",
|
|
869
|
+
9: "failed",
|
|
870
|
+
10: "failed"
|
|
871
|
+
};
|
|
872
|
+
function mapCardcomDealResponseToStatus(dealResponse) {
|
|
873
|
+
switch (dealResponse) {
|
|
874
|
+
case 0:
|
|
875
|
+
return "pending_authorization";
|
|
876
|
+
case 1:
|
|
877
|
+
return "captured";
|
|
878
|
+
case 2:
|
|
879
|
+
return "failed";
|
|
880
|
+
case 3:
|
|
881
|
+
return "failed";
|
|
882
|
+
default:
|
|
883
|
+
return "failed";
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function mapCardcomError(responseCode) {
|
|
887
|
+
const errorMessages = {
|
|
888
|
+
0: "Success",
|
|
889
|
+
1: "General error",
|
|
890
|
+
2: "Invalid API credentials",
|
|
891
|
+
3: "Invalid terminal number",
|
|
892
|
+
4: "Invalid operation type",
|
|
893
|
+
5: "Invalid card details",
|
|
894
|
+
6: "Card declined by issuer",
|
|
895
|
+
7: "Insufficient funds",
|
|
896
|
+
8: "Invalid amount",
|
|
897
|
+
9: "Transaction not found",
|
|
898
|
+
10: "Duplicate transaction",
|
|
899
|
+
11: "Terminal not active",
|
|
900
|
+
12: "CVV validation failed",
|
|
901
|
+
13: "Card expired",
|
|
902
|
+
14: "Invalid currency",
|
|
903
|
+
15: "Operation not supported"
|
|
904
|
+
};
|
|
905
|
+
return errorMessages[responseCode] ?? `Error code ${responseCode}`;
|
|
906
|
+
}
|
|
907
|
+
var CARDCOM_CURRENCY_CODES = {
|
|
908
|
+
ILS: 1,
|
|
909
|
+
USD: 2,
|
|
910
|
+
EUR: 3,
|
|
911
|
+
GBP: 4
|
|
912
|
+
};
|
|
913
|
+
function getCurrencyCode(currency) {
|
|
914
|
+
return CARDCOM_CURRENCY_CODES[currency.toUpperCase()] ?? 1;
|
|
915
|
+
}
|
|
916
|
+
var CARDCOM_LANGUAGE_CODES = {
|
|
917
|
+
en: "en",
|
|
918
|
+
he: "he"
|
|
919
|
+
};
|
|
920
|
+
|
|
921
|
+
// src/providers/cardcom/cardcom-provider.ts
|
|
922
|
+
var CardcomProvider = class {
|
|
923
|
+
name = "cardcom";
|
|
924
|
+
supportedCurrencies = CARDCOM_SUPPORTED_CURRENCIES;
|
|
925
|
+
supportsRecurring = true;
|
|
926
|
+
supportsSplitPayments = false;
|
|
927
|
+
config;
|
|
928
|
+
constructor(config) {
|
|
929
|
+
if (!config.terminalNumber || !config.apiName || !config.apiPassword) {
|
|
930
|
+
throw new Error(
|
|
931
|
+
"CardcomProvider requires terminalNumber, apiName, and apiPassword in config"
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
this.config = config;
|
|
935
|
+
}
|
|
936
|
+
// ==========================================================================
|
|
937
|
+
// Payment Intent Operations
|
|
938
|
+
// ==========================================================================
|
|
939
|
+
async createPaymentIntent(params) {
|
|
940
|
+
try {
|
|
941
|
+
const amountMajor = params.amount.amountMinor / 100;
|
|
942
|
+
const operation = params.captureMethod === "manual" ? 4 /* SUSPEND_DEAL_ONLY */ : params.metadata?.savePaymentMethod ? 2 /* BILL_AND_CREATE_TOKEN */ : 1 /* BILL_ONLY */;
|
|
943
|
+
const request = {
|
|
944
|
+
TerminalNumber: this.config.terminalNumber,
|
|
945
|
+
ApiName: this.config.apiName,
|
|
946
|
+
ApiPassword: this.config.apiPassword,
|
|
947
|
+
Sum: amountMajor,
|
|
948
|
+
CoinID: getCurrencyCode(params.amount.currency),
|
|
949
|
+
Operation: operation,
|
|
950
|
+
Language: "en",
|
|
951
|
+
ReturnUrl: params.returnUrl,
|
|
952
|
+
ErrorUrl: params.returnUrl,
|
|
953
|
+
ProductName: params.description ?? "Payment",
|
|
954
|
+
InternalDealNumber: params.idempotencyKey,
|
|
955
|
+
SendEmail: false
|
|
956
|
+
};
|
|
957
|
+
if (params.metadata?.customerName) {
|
|
958
|
+
request.CustomerName = String(params.metadata.customerName);
|
|
959
|
+
}
|
|
960
|
+
if (params.metadata?.customerEmail) {
|
|
961
|
+
request.Email = String(params.metadata.customerEmail);
|
|
962
|
+
}
|
|
963
|
+
const response = await this.makeRequest(
|
|
964
|
+
CARDCOM_ENDPOINTS.LOW_PROFILE_CREATE,
|
|
965
|
+
request
|
|
966
|
+
);
|
|
967
|
+
if (response.ResponseCode !== 0 || !response.PaymentUrl) {
|
|
968
|
+
return {
|
|
969
|
+
success: false,
|
|
970
|
+
error: mapCardcomError(response.ResponseCode),
|
|
971
|
+
errorCode: String(response.ResponseCode)
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
success: true,
|
|
976
|
+
providerIntentId: response.LowProfileCode,
|
|
977
|
+
redirectUrl: response.PaymentUrl,
|
|
978
|
+
status: "created"
|
|
979
|
+
};
|
|
980
|
+
} catch (error) {
|
|
981
|
+
return this.handleError(error);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
async authorize(params) {
|
|
985
|
+
try {
|
|
986
|
+
const statusResponse = await this.getLowProfileStatus(
|
|
987
|
+
params.providerIntentId
|
|
988
|
+
);
|
|
989
|
+
if (!statusResponse.success || !statusResponse.data) {
|
|
990
|
+
return {
|
|
991
|
+
success: false,
|
|
992
|
+
error: statusResponse.error ?? "Failed to check payment status"
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
const status = statusResponse.data;
|
|
996
|
+
if (status.DealResponse === 1) {
|
|
997
|
+
return {
|
|
998
|
+
success: true,
|
|
999
|
+
authorizationCode: status.InternalDealNumber ?? params.providerIntentId,
|
|
1000
|
+
status: "authorized",
|
|
1001
|
+
captureDeadline: calculateCaptureDeadline2(/* @__PURE__ */ new Date())
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
if (status.DealResponse === 2) {
|
|
1005
|
+
return {
|
|
1006
|
+
success: false,
|
|
1007
|
+
error: "Payment declined",
|
|
1008
|
+
status: "failed"
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
success: false,
|
|
1013
|
+
error: "Payment not yet completed",
|
|
1014
|
+
status: "pending_authorization"
|
|
1015
|
+
};
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
return this.handleError(error);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
async capture(params) {
|
|
1021
|
+
try {
|
|
1022
|
+
const statusResponse = await this.getLowProfileStatus(
|
|
1023
|
+
params.providerIntentId
|
|
1024
|
+
);
|
|
1025
|
+
if (!statusResponse.success || !statusResponse.data) {
|
|
1026
|
+
return {
|
|
1027
|
+
success: false,
|
|
1028
|
+
error: statusResponse.error ?? "Failed to capture payment"
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
const status = statusResponse.data;
|
|
1032
|
+
if (status.DealResponse === 1) {
|
|
1033
|
+
return {
|
|
1034
|
+
success: true,
|
|
1035
|
+
providerTransactionId: status.InternalDealNumber ?? params.providerIntentId,
|
|
1036
|
+
status: "captured",
|
|
1037
|
+
capturedAmount: {
|
|
1038
|
+
amountMinor: Math.round((status.Amount ?? 0) * 100),
|
|
1039
|
+
currency: status.Currency ?? params.amount?.currency ?? "ILS"
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
return {
|
|
1044
|
+
success: false,
|
|
1045
|
+
error: "Payment not authorized for capture",
|
|
1046
|
+
status: mapCardcomDealResponseToStatus(status.DealResponse ?? 3)
|
|
1047
|
+
};
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
return this.handleError(error);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
async void(_params) {
|
|
1053
|
+
return {
|
|
1054
|
+
success: false,
|
|
1055
|
+
error: "Void operation not supported via API. Please use Cardcom merchant dashboard."
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
async refund(params) {
|
|
1059
|
+
try {
|
|
1060
|
+
const refundAmount = params.amount ? params.amount.amountMinor / 100 : void 0;
|
|
1061
|
+
if (!refundAmount) {
|
|
1062
|
+
return {
|
|
1063
|
+
success: false,
|
|
1064
|
+
error: "Refund amount is required"
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
const request = {
|
|
1068
|
+
TerminalNumber: this.config.terminalNumber,
|
|
1069
|
+
ApiName: this.config.apiName,
|
|
1070
|
+
ApiPassword: this.config.apiPassword,
|
|
1071
|
+
InternalDealNumber: params.providerTransactionId,
|
|
1072
|
+
Amount: refundAmount,
|
|
1073
|
+
CoinID: params.amount ? getCurrencyCode(params.amount.currency) : 1
|
|
1074
|
+
};
|
|
1075
|
+
const response = await this.makeRequest(
|
|
1076
|
+
CARDCOM_ENDPOINTS.REFUND,
|
|
1077
|
+
request
|
|
1078
|
+
);
|
|
1079
|
+
if (response.ResponseCode !== 0) {
|
|
1080
|
+
return {
|
|
1081
|
+
success: false,
|
|
1082
|
+
error: mapCardcomError(response.ResponseCode)
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
return {
|
|
1086
|
+
success: true,
|
|
1087
|
+
providerRefundId: response.InternalDealNumber ?? params.providerTransactionId,
|
|
1088
|
+
refundedAmount: {
|
|
1089
|
+
amountMinor: Math.round((response.Amount ?? 0) * 100),
|
|
1090
|
+
currency: params.amount?.currency ?? "ILS"
|
|
1091
|
+
},
|
|
1092
|
+
status: "succeeded"
|
|
1093
|
+
};
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
return this.handleError(error);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
// ==========================================================================
|
|
1099
|
+
// Payment Method Tokenization
|
|
1100
|
+
// ==========================================================================
|
|
1101
|
+
async createSetupIntent(params) {
|
|
1102
|
+
try {
|
|
1103
|
+
const request = {
|
|
1104
|
+
TerminalNumber: this.config.terminalNumber,
|
|
1105
|
+
ApiName: this.config.apiName,
|
|
1106
|
+
ApiPassword: this.config.apiPassword,
|
|
1107
|
+
Sum: 0,
|
|
1108
|
+
Operation: 3 /* CREATE_TOKEN_ONLY */,
|
|
1109
|
+
Language: "en",
|
|
1110
|
+
InternalDealNumber: `setup_${params.userId}_${Date.now()}`
|
|
1111
|
+
};
|
|
1112
|
+
const response = await this.makeRequest(
|
|
1113
|
+
CARDCOM_ENDPOINTS.LOW_PROFILE_CREATE,
|
|
1114
|
+
request
|
|
1115
|
+
);
|
|
1116
|
+
if (response.ResponseCode !== 0 || !response.PaymentUrl) {
|
|
1117
|
+
return {
|
|
1118
|
+
success: false,
|
|
1119
|
+
error: mapCardcomError(response.ResponseCode)
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
success: true,
|
|
1124
|
+
setupIntentId: response.LowProfileCode,
|
|
1125
|
+
clientSecret: response.PaymentUrl
|
|
1126
|
+
};
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
return this.handleError(error);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
async savePaymentMethod(params) {
|
|
1132
|
+
try {
|
|
1133
|
+
const lowProfileCode = params.setupData.lowProfileCode;
|
|
1134
|
+
if (!lowProfileCode) {
|
|
1135
|
+
return {
|
|
1136
|
+
success: false,
|
|
1137
|
+
error: "Low profile code is required"
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
const statusResponse = await this.getLowProfileStatus(lowProfileCode);
|
|
1141
|
+
if (!statusResponse.success || !statusResponse.data) {
|
|
1142
|
+
return {
|
|
1143
|
+
success: false,
|
|
1144
|
+
error: statusResponse.error ?? "Failed to retrieve payment method"
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
const status = statusResponse.data;
|
|
1148
|
+
if (!status.Token) {
|
|
1149
|
+
return {
|
|
1150
|
+
success: false,
|
|
1151
|
+
error: "No token created"
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
const [expMonth, expYear] = (status.CardExpiration ?? "/").split("/");
|
|
1155
|
+
return {
|
|
1156
|
+
success: true,
|
|
1157
|
+
paymentMethodId: status.Token,
|
|
1158
|
+
cardBrand: status.CardType ?? "unknown",
|
|
1159
|
+
cardLast4: status.CardMask?.slice(-4),
|
|
1160
|
+
cardExpMonth: expMonth?.padStart(2, "0"),
|
|
1161
|
+
cardExpYear: expYear ? `20${expYear}` : void 0,
|
|
1162
|
+
cardBin: status.CardBin
|
|
1163
|
+
};
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
return this.handleError(error);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
async deletePaymentMethod(_paymentMethodId) {
|
|
1169
|
+
return {
|
|
1170
|
+
success: true
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
// ==========================================================================
|
|
1174
|
+
// Customer Management
|
|
1175
|
+
// ==========================================================================
|
|
1176
|
+
async createCustomer(params) {
|
|
1177
|
+
return {
|
|
1178
|
+
success: true,
|
|
1179
|
+
customerId: params.userId
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
async getOrCreateCustomer(userId, email) {
|
|
1183
|
+
return this.createCustomer({ userId, email });
|
|
1184
|
+
}
|
|
1185
|
+
// ==========================================================================
|
|
1186
|
+
// Health & Status
|
|
1187
|
+
// ==========================================================================
|
|
1188
|
+
async getHealth() {
|
|
1189
|
+
const start = Date.now();
|
|
1190
|
+
try {
|
|
1191
|
+
const request = {
|
|
1192
|
+
TerminalNumber: this.config.terminalNumber,
|
|
1193
|
+
ApiName: this.config.apiName,
|
|
1194
|
+
ApiPassword: this.config.apiPassword,
|
|
1195
|
+
Sum: 1,
|
|
1196
|
+
Operation: 1 /* BILL_ONLY */,
|
|
1197
|
+
InternalDealNumber: `health_check_${Date.now()}`
|
|
1198
|
+
};
|
|
1199
|
+
const response = await this.makeRequest(
|
|
1200
|
+
CARDCOM_ENDPOINTS.LOW_PROFILE_CREATE,
|
|
1201
|
+
request
|
|
1202
|
+
);
|
|
1203
|
+
const healthy = response.ResponseCode === 0 || response.ResponseCode === 1;
|
|
1204
|
+
return {
|
|
1205
|
+
provider: "cardcom",
|
|
1206
|
+
healthy,
|
|
1207
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
1208
|
+
avgLatencyMs: Date.now() - start,
|
|
1209
|
+
circuitBreakerOpen: false
|
|
1210
|
+
};
|
|
1211
|
+
} catch {
|
|
1212
|
+
return {
|
|
1213
|
+
provider: "cardcom",
|
|
1214
|
+
healthy: false,
|
|
1215
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
1216
|
+
circuitBreakerOpen: false
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
validateWebhookSignature(payload, signature) {
|
|
1221
|
+
if (!this.config.webhookSecret) {
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
try {
|
|
1225
|
+
const expectedSignature = crypto.createHmac("sha256", this.config.webhookSecret).update(payload).digest("hex");
|
|
1226
|
+
return crypto.timingSafeEqual(
|
|
1227
|
+
Buffer.from(signature),
|
|
1228
|
+
Buffer.from(expectedSignature)
|
|
1229
|
+
);
|
|
1230
|
+
} catch {
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
async getPaymentIntentStatus(providerIntentId) {
|
|
1235
|
+
try {
|
|
1236
|
+
const result = await this.getLowProfileStatus(providerIntentId);
|
|
1237
|
+
if (!result.success || !result.data) {
|
|
1238
|
+
return {
|
|
1239
|
+
status: "unknown",
|
|
1240
|
+
error: result.error
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
const status = mapCardcomDealResponseToStatus(
|
|
1244
|
+
result.data.DealResponse ?? 0
|
|
1245
|
+
);
|
|
1246
|
+
return { status };
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
return {
|
|
1249
|
+
status: "unknown",
|
|
1250
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
// ==========================================================================
|
|
1255
|
+
// Helper Methods
|
|
1256
|
+
// ==========================================================================
|
|
1257
|
+
async makeRequest(endpoint, data) {
|
|
1258
|
+
const url = `${CARDCOM_API_BASE}${endpoint}`;
|
|
1259
|
+
const response = await fetch(url, {
|
|
1260
|
+
method: "POST",
|
|
1261
|
+
headers: {
|
|
1262
|
+
"Content-Type": "application/json"
|
|
1263
|
+
},
|
|
1264
|
+
body: JSON.stringify(data)
|
|
1265
|
+
});
|
|
1266
|
+
if (!response.ok) {
|
|
1267
|
+
throw new Error(`Cardcom API error: ${response.status} ${response.statusText}`);
|
|
1268
|
+
}
|
|
1269
|
+
return response.json();
|
|
1270
|
+
}
|
|
1271
|
+
async getLowProfileStatus(lowProfileCode) {
|
|
1272
|
+
try {
|
|
1273
|
+
const params = new URLSearchParams({
|
|
1274
|
+
terminalnumber: this.config.terminalNumber,
|
|
1275
|
+
lowprofilecode: lowProfileCode,
|
|
1276
|
+
username: this.config.apiName
|
|
1277
|
+
});
|
|
1278
|
+
const url = `${CARDCOM_API_BASE}${CARDCOM_ENDPOINTS.LOW_PROFILE_STATUS}?${params}`;
|
|
1279
|
+
const response = await fetch(url, {
|
|
1280
|
+
method: "GET"
|
|
1281
|
+
});
|
|
1282
|
+
if (!response.ok) {
|
|
1283
|
+
return {
|
|
1284
|
+
success: false,
|
|
1285
|
+
error: `Status check failed: ${response.status}`
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
const data = await response.json();
|
|
1289
|
+
if (data.ResponseCode !== 0) {
|
|
1290
|
+
return {
|
|
1291
|
+
success: false,
|
|
1292
|
+
error: mapCardcomError(data.ResponseCode)
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
return { success: true, data };
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
return {
|
|
1298
|
+
success: false,
|
|
1299
|
+
error: error instanceof Error ? error.message : "Status check failed"
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
handleError(error) {
|
|
1304
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
1305
|
+
return {
|
|
1306
|
+
success: false,
|
|
1307
|
+
error: errorMessage
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
// src/providers/cardcom/cardcom-webhook-handler.ts
|
|
1313
|
+
var CardcomWebhookHandler = class {
|
|
1314
|
+
provider = "cardcom";
|
|
1315
|
+
supportedEventTypes = CARDCOM_WEBHOOK_EVENTS;
|
|
1316
|
+
parseEvent(rawPayload) {
|
|
1317
|
+
try {
|
|
1318
|
+
const params = rawPayload;
|
|
1319
|
+
const responseCode = parseInt(params.ResponseCode ?? "1", 10);
|
|
1320
|
+
const dealResponse = parseInt(params.DealResponse ?? "0", 10);
|
|
1321
|
+
const lowProfileCode = params.LowProfileCode ?? "";
|
|
1322
|
+
const internalDealNumber = params.InternalDealNumber ?? "";
|
|
1323
|
+
if (!lowProfileCode && !internalDealNumber) {
|
|
1324
|
+
return {
|
|
1325
|
+
success: false,
|
|
1326
|
+
error: "Missing LowProfileCode or InternalDealNumber in callback"
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
let eventType;
|
|
1330
|
+
if (dealResponse === 1) {
|
|
1331
|
+
eventType = "payment.completed";
|
|
1332
|
+
} else if (dealResponse === 2) {
|
|
1333
|
+
eventType = "payment.declined";
|
|
1334
|
+
} else if (dealResponse === 0 && responseCode === 0) {
|
|
1335
|
+
eventType = "payment.authorized";
|
|
1336
|
+
} else {
|
|
1337
|
+
eventType = "payment.declined";
|
|
1338
|
+
}
|
|
1339
|
+
const status = mapCardcomDealResponseToStatus(dealResponse);
|
|
1340
|
+
const amountString = params.Amount ?? "0";
|
|
1341
|
+
const amountMajor = parseFloat(amountString);
|
|
1342
|
+
const amountMinor = Math.round(amountMajor * 100);
|
|
1343
|
+
const parsed = {
|
|
1344
|
+
provider: "cardcom",
|
|
1345
|
+
eventId: `${lowProfileCode}_${internalDealNumber}_${Date.now()}`,
|
|
1346
|
+
eventType,
|
|
1347
|
+
providerTransactionId: internalDealNumber || lowProfileCode,
|
|
1348
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1349
|
+
rawPayload,
|
|
1350
|
+
newStatus: status,
|
|
1351
|
+
amountMinor,
|
|
1352
|
+
currency: params.Currency ?? "ILS"
|
|
1353
|
+
};
|
|
1354
|
+
if (dealResponse === 2 || responseCode !== 0) {
|
|
1355
|
+
parsed.error = {
|
|
1356
|
+
code: String(responseCode),
|
|
1357
|
+
message: CARDCOM_DEAL_RESPONSE_ACTIONS[dealResponse] ?? "Payment failed"
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
return { success: true, event: parsed };
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
return {
|
|
1363
|
+
success: false,
|
|
1364
|
+
error: error instanceof Error ? error.message : "Parse error"
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
async processEvent(event) {
|
|
1369
|
+
const action = this.getActionForEvent(event.eventType);
|
|
1370
|
+
if (action === "ignored") {
|
|
1371
|
+
return {
|
|
1372
|
+
success: true,
|
|
1373
|
+
action: "ignored_event_type"
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
success: true,
|
|
1378
|
+
transactionId: event.providerTransactionId,
|
|
1379
|
+
action: "status_updated"
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
canHandle(eventType) {
|
|
1383
|
+
return this.supportedEventTypes.includes(
|
|
1384
|
+
eventType
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
async reconcile(_transactionId, _providerTransactionId) {
|
|
1388
|
+
return {
|
|
1389
|
+
reconciled: false,
|
|
1390
|
+
finalStatus: "created",
|
|
1391
|
+
source: "provider_query",
|
|
1392
|
+
statusChanged: false
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
mapEventType(providerEventType) {
|
|
1396
|
+
return providerEventType;
|
|
1397
|
+
}
|
|
1398
|
+
mapStatus(providerStatus) {
|
|
1399
|
+
const dealResponse = parseInt(providerStatus, 10);
|
|
1400
|
+
if (isNaN(dealResponse)) {
|
|
1401
|
+
return null;
|
|
1402
|
+
}
|
|
1403
|
+
return mapCardcomDealResponseToStatus(dealResponse);
|
|
1404
|
+
}
|
|
1405
|
+
getActionForEvent(eventType) {
|
|
1406
|
+
switch (eventType) {
|
|
1407
|
+
case "payment.completed":
|
|
1408
|
+
case "payment.declined":
|
|
1409
|
+
case "payment.authorized":
|
|
1410
|
+
return "status_update";
|
|
1411
|
+
default:
|
|
1412
|
+
return "ignored";
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
function validateCardcomCallback(params) {
|
|
1417
|
+
const requiredFields = ["ResponseCode", "LowProfileCode"];
|
|
1418
|
+
for (const field of requiredFields) {
|
|
1419
|
+
if (!params[field]) {
|
|
1420
|
+
return {
|
|
1421
|
+
valid: false,
|
|
1422
|
+
error: `Missing required field: ${field}`
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
const responseCode = parseInt(String(params.ResponseCode), 10);
|
|
1427
|
+
if (isNaN(responseCode)) {
|
|
1428
|
+
return {
|
|
1429
|
+
valid: false,
|
|
1430
|
+
error: "Invalid ResponseCode format"
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
return { valid: true };
|
|
1434
|
+
}
|
|
1435
|
+
function parseCardcomCallbackUrl(url) {
|
|
1436
|
+
try {
|
|
1437
|
+
const urlObj = new URL(url);
|
|
1438
|
+
const params = {};
|
|
1439
|
+
params.ResponseCode = urlObj.searchParams.get("ResponseCode") ?? void 0;
|
|
1440
|
+
params.LowProfileCode = urlObj.searchParams.get("LowProfileCode") ?? void 0;
|
|
1441
|
+
params.DealResponse = urlObj.searchParams.get("DealResponse") ?? void 0;
|
|
1442
|
+
params.OperationResponse = urlObj.searchParams.get("OperationResponse") ?? void 0;
|
|
1443
|
+
params.InternalDealNumber = urlObj.searchParams.get("InternalDealNumber") ?? void 0;
|
|
1444
|
+
params.Amount = urlObj.searchParams.get("Amount") ?? void 0;
|
|
1445
|
+
params.Currency = urlObj.searchParams.get("Currency") ?? void 0;
|
|
1446
|
+
params.CardMask = urlObj.searchParams.get("CardMask") ?? void 0;
|
|
1447
|
+
params.Token = urlObj.searchParams.get("Token") ?? void 0;
|
|
1448
|
+
return params;
|
|
1449
|
+
} catch {
|
|
1450
|
+
return {};
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
function isCardcomCallbackSuccess(params) {
|
|
1454
|
+
const responseCode = parseInt(params.ResponseCode ?? "1", 10);
|
|
1455
|
+
const dealResponse = parseInt(params.DealResponse ?? "0", 10);
|
|
1456
|
+
return responseCode === 0 && dealResponse === 1;
|
|
1457
|
+
}
|
|
1458
|
+
function isCardcomCallbackAuthorized(params) {
|
|
1459
|
+
const responseCode = parseInt(params.ResponseCode ?? "1", 10);
|
|
1460
|
+
const dealResponse = parseInt(params.DealResponse ?? "0", 10);
|
|
1461
|
+
return responseCode === 0 && (dealResponse === 0 || dealResponse === 1);
|
|
1462
|
+
}
|
|
1463
|
+
function getCardcomCallbackError(params) {
|
|
1464
|
+
const responseCode = parseInt(params.ResponseCode ?? "1", 10);
|
|
1465
|
+
const dealResponse = parseInt(params.DealResponse ?? "0", 10);
|
|
1466
|
+
if (responseCode === 0 && dealResponse === 1) {
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
if (dealResponse === 2) {
|
|
1470
|
+
return "Payment declined by card issuer";
|
|
1471
|
+
}
|
|
1472
|
+
if (responseCode !== 0) {
|
|
1473
|
+
return `Payment failed with code ${responseCode}`;
|
|
1474
|
+
}
|
|
1475
|
+
return "Payment processing error";
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// src/routing/routing-rules.ts
|
|
1479
|
+
var ISRAELI_BIN_RANGES = [
|
|
1480
|
+
// Isracard
|
|
1481
|
+
{ start: "458000", end: "458999", issuer: "Isracard", country: "IL" },
|
|
1482
|
+
{ start: "480000", end: "480999", issuer: "Isracard", country: "IL" },
|
|
1483
|
+
// Cal (Visa Cal)
|
|
1484
|
+
{ start: "532600", end: "532699", issuer: "Cal", country: "IL" },
|
|
1485
|
+
{ start: "557050", end: "557059", issuer: "Cal", country: "IL" },
|
|
1486
|
+
// Leumi Card
|
|
1487
|
+
{ start: "589200", end: "589299", issuer: "Leumi Card", country: "IL" },
|
|
1488
|
+
// Diners Israel
|
|
1489
|
+
{ start: "363700", end: "363799", issuer: "Diners Israel", country: "IL" },
|
|
1490
|
+
// Max (Leumi)
|
|
1491
|
+
{ start: "491861", end: "491861", issuer: "Max", country: "IL" },
|
|
1492
|
+
{ start: "458600", end: "458699", issuer: "Max", country: "IL" }
|
|
1493
|
+
];
|
|
1494
|
+
var ISRAELI_CARD_BIN_RULES = [
|
|
1495
|
+
{
|
|
1496
|
+
ranges: ISRAELI_BIN_RANGES.map((r) => ({
|
|
1497
|
+
start: r.start,
|
|
1498
|
+
end: r.end,
|
|
1499
|
+
issuer: r.issuer,
|
|
1500
|
+
country: r.country
|
|
1501
|
+
})),
|
|
1502
|
+
preferredProvider: "hyp",
|
|
1503
|
+
priority: 1
|
|
1504
|
+
}
|
|
1505
|
+
];
|
|
1506
|
+
var ISRAELI_PROVIDER_PRIORITIES = [
|
|
1507
|
+
{
|
|
1508
|
+
provider: "hyp",
|
|
1509
|
+
priority: 1,
|
|
1510
|
+
maxFeePercent: 1.8,
|
|
1511
|
+
supportsCurrency: ["ILS", "USD", "EUR"],
|
|
1512
|
+
supportsRecurring: true,
|
|
1513
|
+
isLocalGateway: true
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
provider: "cardcom",
|
|
1517
|
+
priority: 2,
|
|
1518
|
+
maxFeePercent: 2,
|
|
1519
|
+
supportsCurrency: ["ILS", "USD", "EUR"],
|
|
1520
|
+
supportsRecurring: true,
|
|
1521
|
+
isLocalGateway: true
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
provider: "stripe",
|
|
1525
|
+
priority: 3,
|
|
1526
|
+
maxFeePercent: 2.9,
|
|
1527
|
+
supportsCurrency: ["USD", "EUR", "GBP", "ILS", "CAD", "AUD"],
|
|
1528
|
+
supportsRecurring: true,
|
|
1529
|
+
isLocalGateway: false
|
|
1530
|
+
}
|
|
1531
|
+
];
|
|
1532
|
+
var ISRAELI_ROUTING_RULES = {
|
|
1533
|
+
cardBinRules: ISRAELI_CARD_BIN_RULES,
|
|
1534
|
+
providerPriorities: ISRAELI_PROVIDER_PRIORITIES
|
|
1535
|
+
};
|
|
1536
|
+
function isIsraeliCard(bin) {
|
|
1537
|
+
if (!bin || bin.length < 6) return false;
|
|
1538
|
+
const binPrefix = bin.substring(0, 6);
|
|
1539
|
+
return ISRAELI_BIN_RANGES.some(
|
|
1540
|
+
(range) => binPrefix >= range.start && binPrefix <= range.end
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
function getCardIssuer(bin) {
|
|
1544
|
+
if (!bin || bin.length < 6) return null;
|
|
1545
|
+
const binPrefix = bin.substring(0, 6);
|
|
1546
|
+
const range = ISRAELI_BIN_RANGES.find(
|
|
1547
|
+
(r) => binPrefix >= r.start && binPrefix <= r.end
|
|
1548
|
+
);
|
|
1549
|
+
return range?.issuer ?? null;
|
|
1550
|
+
}
|
|
1551
|
+
function getOptimalProvider(isIsraeli, currency, requiresRecurring, availableProviders) {
|
|
1552
|
+
const candidates = ISRAELI_PROVIDER_PRIORITIES.filter(
|
|
1553
|
+
(p) => availableProviders.includes(p.provider)
|
|
1554
|
+
);
|
|
1555
|
+
if (candidates.length === 0) return null;
|
|
1556
|
+
const suitable = candidates.filter((p) => {
|
|
1557
|
+
if (!p.supportsCurrency.includes(currency)) return false;
|
|
1558
|
+
if (requiresRecurring && !p.supportsRecurring) return false;
|
|
1559
|
+
return true;
|
|
1560
|
+
});
|
|
1561
|
+
if (suitable.length === 0) {
|
|
1562
|
+
return candidates[0]?.provider ?? null;
|
|
1563
|
+
}
|
|
1564
|
+
if (isIsraeli) {
|
|
1565
|
+
const localProviders = suitable.filter((p) => p.isLocalGateway);
|
|
1566
|
+
if (localProviders.length > 0) {
|
|
1567
|
+
return localProviders.sort((a, b) => a.priority - b.priority)[0].provider;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
return suitable.sort((a, b) => a.priority - b.priority)[0].provider;
|
|
1571
|
+
}
|
|
1572
|
+
function getFallbackProviders(primaryProvider, availableProviders) {
|
|
1573
|
+
return ISRAELI_PROVIDER_PRIORITIES.filter(
|
|
1574
|
+
(p) => p.provider !== primaryProvider && availableProviders.includes(p.provider)
|
|
1575
|
+
).sort((a, b) => a.priority - b.priority).map((p) => p.provider);
|
|
1576
|
+
}
|
|
1577
|
+
function getProviderFeePercent(provider) {
|
|
1578
|
+
const config = ISRAELI_PROVIDER_PRIORITIES.find((p) => p.provider === provider);
|
|
1579
|
+
return config?.maxFeePercent ?? 3;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
// src/factory.ts
|
|
1583
|
+
function addIsraeliProviders(services, config) {
|
|
1584
|
+
if (config.hyp) {
|
|
1585
|
+
services.providers.set("hyp", new HypProvider(config.hyp));
|
|
1586
|
+
services.webhookHandlers.set("hyp", new HypWebhookHandler());
|
|
1587
|
+
}
|
|
1588
|
+
if (config.cardcom) {
|
|
1589
|
+
services.providers.set("cardcom", new CardcomProvider(config.cardcom));
|
|
1590
|
+
services.webhookHandlers.set("cardcom", new CardcomWebhookHandler());
|
|
1591
|
+
}
|
|
1592
|
+
return services;
|
|
1593
|
+
}
|
|
1594
|
+
export {
|
|
1595
|
+
CARDCOM_API_BASE,
|
|
1596
|
+
CARDCOM_CURRENCY_CODES,
|
|
1597
|
+
CARDCOM_DEAL_RESPONSE_ACTIONS,
|
|
1598
|
+
CARDCOM_ENDPOINTS,
|
|
1599
|
+
CARDCOM_LANGUAGE_CODES,
|
|
1600
|
+
CARDCOM_RESPONSE_CODE_MAP,
|
|
1601
|
+
CARDCOM_SUPPORTED_CURRENCIES,
|
|
1602
|
+
CARDCOM_WEBHOOK_EVENTS,
|
|
1603
|
+
CardcomOperation,
|
|
1604
|
+
CardcomProvider,
|
|
1605
|
+
CardcomTransactionType,
|
|
1606
|
+
CardcomWebhookHandler,
|
|
1607
|
+
DEFAULT_HYP_ENDPOINTS,
|
|
1608
|
+
HYP_CREDIT_TYPES,
|
|
1609
|
+
HYP_ERROR_MAP,
|
|
1610
|
+
HYP_RESULT_CODE_MAP,
|
|
1611
|
+
HYP_SUPPORTED_CURRENCIES,
|
|
1612
|
+
HYP_TRANSACTION_CODES,
|
|
1613
|
+
HYP_TRANSACTION_TYPES,
|
|
1614
|
+
HYP_VALIDATION_MODES,
|
|
1615
|
+
HypProvider,
|
|
1616
|
+
HypWebhookHandler,
|
|
1617
|
+
ISRAELI_BIN_RANGES,
|
|
1618
|
+
ISRAELI_ROUTING_RULES,
|
|
1619
|
+
addIsraeliProviders,
|
|
1620
|
+
createHypWebhookHandler,
|
|
1621
|
+
formatCardExpiration,
|
|
1622
|
+
formatHypAmount,
|
|
1623
|
+
getCardIssuer,
|
|
1624
|
+
getCardcomCallbackError,
|
|
1625
|
+
getCurrencyCode,
|
|
1626
|
+
getFallbackProviders,
|
|
1627
|
+
getOptimalProvider,
|
|
1628
|
+
getProviderFeePercent,
|
|
1629
|
+
isCardcomCallbackAuthorized,
|
|
1630
|
+
isCardcomCallbackSuccess,
|
|
1631
|
+
isHypSuccess,
|
|
1632
|
+
isHypSupportedCurrency,
|
|
1633
|
+
isIsraeliCard,
|
|
1634
|
+
mapCardcomDealResponseToStatus,
|
|
1635
|
+
mapCardcomError,
|
|
1636
|
+
mapHypError,
|
|
1637
|
+
mapHypStatus,
|
|
1638
|
+
parseCardExpiration,
|
|
1639
|
+
parseCardcomCallbackUrl,
|
|
1640
|
+
validateCardcomCallback
|
|
1641
|
+
};
|
|
1642
|
+
//# sourceMappingURL=index.js.map
|