ti2-ventrata 1.0.36 → 1.0.38
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/index.js +137 -53
- package/index.test.js +1 -1
- package/package.json +1 -1
- package/resolvers/availability.js +2 -0
- package/resolvers/booking.js +3 -0
- package/resolvers/product.js +1 -0
- package/utils/countrymapping.js +206 -0
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const { translateProduct } = require('./resolvers/product');
|
|
|
8
8
|
const { translateAvailability } = require('./resolvers/availability');
|
|
9
9
|
const { translateBooking } = require('./resolvers/booking');
|
|
10
10
|
const { translatePickupPoint } = require('./resolvers/pickup-point');
|
|
11
|
+
const { validateCountry } = require('./utils/countrymapping');
|
|
11
12
|
|
|
12
13
|
const CONCURRENCY = 3; // is this ok ?
|
|
13
14
|
|
|
@@ -71,6 +72,24 @@ class Plugin {
|
|
|
71
72
|
this.errorPathsAxiosAny = () => ([]); // 200's that should be errors
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
static getSettlementMethod(reference, availableSettlementMethods = []) {
|
|
76
|
+
// Determine settlement method from product's available methods
|
|
77
|
+
let settlementMethod = 'DEFERRED'; // default fallback
|
|
78
|
+
|
|
79
|
+
if (reference && availableSettlementMethods.includes('VOUCHER')) {
|
|
80
|
+
settlementMethod = 'VOUCHER';
|
|
81
|
+
} else if (reference && availableSettlementMethods.includes('DIRECT')) {
|
|
82
|
+
settlementMethod = 'DIRECT';
|
|
83
|
+
} else if (availableSettlementMethods.includes('DEFERRED')) {
|
|
84
|
+
settlementMethod = 'DEFERRED';
|
|
85
|
+
} else if (availableSettlementMethods.length > 0) {
|
|
86
|
+
// Use the first available settlement method if none of the preferred ones are available
|
|
87
|
+
[settlementMethod] = availableSettlementMethods;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return settlementMethod;
|
|
91
|
+
}
|
|
92
|
+
|
|
74
93
|
async validateToken({
|
|
75
94
|
axios,
|
|
76
95
|
token: {
|
|
@@ -223,12 +242,22 @@ class Plugin {
|
|
|
223
242
|
const url = `${endpoint || this.endpoint}/availability`;
|
|
224
243
|
const availability = (
|
|
225
244
|
await Promise.map(productIds, async (productId, ix) => {
|
|
245
|
+
// Fetch product data to get settlement methods
|
|
246
|
+
const productUrl = `${endpoint || this.endpoint}/products/${productId}`;
|
|
247
|
+
const productResult = await axios({
|
|
248
|
+
method: 'get',
|
|
249
|
+
url: productUrl,
|
|
250
|
+
headers,
|
|
251
|
+
});
|
|
252
|
+
const settlementMethods = R.path(['data', 'settlementMethods'], productResult) || [];
|
|
253
|
+
// eslint-disable-next-line max-len
|
|
254
|
+
const unitsWithId = units[ix].filter(u => u.unitId).map(u => ({ id: u.unitId, quantity: u.quantity }));
|
|
226
255
|
const data = {
|
|
227
256
|
productId,
|
|
228
257
|
optionId: optionIds[ix],
|
|
229
258
|
localDateStart,
|
|
230
259
|
localDateEnd,
|
|
231
|
-
|
|
260
|
+
...(unitsWithId && unitsWithId.length > 0 && { units: unitsWithId }),
|
|
232
261
|
};
|
|
233
262
|
if (currency) data.currency = currency;
|
|
234
263
|
const result = R.path(['data'], await axios({
|
|
@@ -247,6 +276,7 @@ class Plugin {
|
|
|
247
276
|
currency,
|
|
248
277
|
unitsWithQuantity: units[ix],
|
|
249
278
|
jwtKey: this.jwtKey,
|
|
279
|
+
settlementMethods,
|
|
250
280
|
},
|
|
251
281
|
}));
|
|
252
282
|
}, { concurrency: CONCURRENCY })
|
|
@@ -297,18 +327,28 @@ class Plugin {
|
|
|
297
327
|
acceptLanguage,
|
|
298
328
|
resellerId,
|
|
299
329
|
});
|
|
300
|
-
const url = `${endpoint || this.endpoint}/availability/calendar`;
|
|
301
330
|
const availability = (
|
|
302
331
|
await Promise.map(productIds, async (productId, ix) => {
|
|
332
|
+
// Fetch product data to get settlement methods
|
|
333
|
+
const productUrl = `${endpoint || this.endpoint}/products/${productId}`;
|
|
334
|
+
const productResult = await axios({
|
|
335
|
+
method: 'get',
|
|
336
|
+
url: productUrl,
|
|
337
|
+
headers,
|
|
338
|
+
});
|
|
339
|
+
const settlementMethods = R.path(['data', 'settlementMethods'], productResult) || [];
|
|
340
|
+
// eslint-disable-next-line max-len
|
|
341
|
+
const unitsWithId = units[ix].filter(u => u.unitId).map(u => ({ id: u.unitId, quantity: u.quantity }));
|
|
303
342
|
const data = {
|
|
304
343
|
productId,
|
|
305
344
|
optionId: optionIds[ix],
|
|
306
345
|
localDateStart,
|
|
307
346
|
localDateEnd,
|
|
308
347
|
// units is required here to get the total pricing for the calendar
|
|
309
|
-
|
|
348
|
+
...(unitsWithId && unitsWithId.length > 0 && { units: unitsWithId }),
|
|
310
349
|
};
|
|
311
350
|
if (currency) data.currency = currency;
|
|
351
|
+
const url = `${endpoint || this.endpoint}/availability/calendar`;
|
|
312
352
|
const result = await axios({
|
|
313
353
|
method: 'post',
|
|
314
354
|
url,
|
|
@@ -319,6 +359,14 @@ class Plugin {
|
|
|
319
359
|
rootValue: avail,
|
|
320
360
|
typeDefs: availTypeDefs,
|
|
321
361
|
query: availQuery,
|
|
362
|
+
variableValues: {
|
|
363
|
+
productId,
|
|
364
|
+
optionId: optionIds[ix],
|
|
365
|
+
currency,
|
|
366
|
+
unitsWithQuantity: units[ix],
|
|
367
|
+
jwtKey: this.jwtKey,
|
|
368
|
+
settlementMethods,
|
|
369
|
+
},
|
|
322
370
|
}));
|
|
323
371
|
}, { concurrency: CONCURRENCY })
|
|
324
372
|
);
|
|
@@ -359,68 +407,100 @@ class Plugin {
|
|
|
359
407
|
acceptLanguage,
|
|
360
408
|
});
|
|
361
409
|
const dataForCreateBooking = await jwt.verify(availabilityKey, this.jwtKey);
|
|
410
|
+
// if customFieldValues contains perUnitItem fields
|
|
411
|
+
// we need to overwrite unitItems from the availabilityKey
|
|
412
|
+
if (customFieldValues && customFieldValues.length) {
|
|
413
|
+
const productCFV = customFieldValues.filter(o => !R.isNil(o.value) && !o.field.isPerUnitItem);
|
|
414
|
+
const unitItemCFV = customFieldValues.filter(o => !R.isNil(o.value) && o.field.isPerUnitItem);
|
|
415
|
+
if (productCFV.length) {
|
|
416
|
+
dataForCreateBooking.questionAnswers = productCFV.map(o => ({
|
|
417
|
+
questionId: o.field.id,
|
|
418
|
+
value: o.value,
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
421
|
+
if (unitItemCFV.length) {
|
|
422
|
+
dataForCreateBooking.unitItems = R.call(R.compose(
|
|
423
|
+
R.map(arr => ({
|
|
424
|
+
unitId: arr[0].field.unitId,
|
|
425
|
+
questionAnswers: arr.map(o => ({
|
|
426
|
+
questionId: o.field.id.split('|')[0],
|
|
427
|
+
value: o.value,
|
|
428
|
+
})),
|
|
429
|
+
})),
|
|
430
|
+
R.values,
|
|
431
|
+
R.groupBy(o => {
|
|
432
|
+
const [, unitItemIndex] = o.field.id.split('|');
|
|
433
|
+
return unitItemIndex;
|
|
434
|
+
}),
|
|
435
|
+
), unitItemCFV);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const settlementMethod = Plugin.getSettlementMethod(
|
|
440
|
+
reference,
|
|
441
|
+
dataForCreateBooking.settlementMethods,
|
|
442
|
+
);
|
|
362
443
|
let booking = R.path(['data'], await axios({
|
|
363
444
|
method: rebookingId ? 'patch' : 'post',
|
|
364
445
|
url: `${endpoint || this.endpoint}/bookings${rebookingId ? `/${rebookingId}` : ''}`,
|
|
365
446
|
data: {
|
|
366
447
|
orderId,
|
|
367
|
-
settlementMethod
|
|
448
|
+
settlementMethod,
|
|
368
449
|
...dataForCreateBooking,
|
|
369
450
|
notes,
|
|
370
451
|
...(pickupPoint ? { pickupRequested: true, pickupPointId: pickupPoint } : {}),
|
|
371
452
|
},
|
|
372
453
|
headers,
|
|
373
454
|
}));
|
|
374
|
-
const answers = customFieldValues && customFieldValues.length
|
|
375
|
-
? customFieldValues.filter(o => !R.isNil(o.value)).map(o => ({
|
|
376
|
-
questionId: o.field.id,
|
|
377
|
-
value: o.value,
|
|
378
|
-
})) : [];
|
|
379
|
-
// for answers to custom fields, ventrata suggest to modify the booking
|
|
380
|
-
if (answers.length) {
|
|
381
|
-
let modifierBooking = {
|
|
382
|
-
questionAnswers: booking.questionAnswers.map(o => {
|
|
383
|
-
const answer = answers.find(a => a.questionId === o.questionId);
|
|
384
|
-
return answer || o;
|
|
385
|
-
}),
|
|
386
|
-
unitItems: booking.unitItems.map(u => ({
|
|
387
|
-
...u,
|
|
388
|
-
questionAnswers: u.questionAnswers.map(o => {
|
|
389
|
-
const answer = answers.find(a => a.questionId === o.questionId);
|
|
390
|
-
return answer || o;
|
|
391
|
-
}),
|
|
392
|
-
})),
|
|
393
|
-
};
|
|
394
|
-
if (!modifierBooking.unitItems.find(u => u.questionAnswers && u.questionAnswers.length)) {
|
|
395
|
-
modifierBooking = R.omit(['unitItems'], modifierBooking);
|
|
396
|
-
}
|
|
397
|
-
booking = R.path(['data'], await axios({
|
|
398
|
-
method: 'patch',
|
|
399
|
-
url: `${endpoint || this.endpoint}/bookings/${booking.uuid}`,
|
|
400
|
-
data: modifierBooking,
|
|
401
|
-
headers,
|
|
402
|
-
}));
|
|
403
|
-
}
|
|
404
455
|
// for booking update, we may not need to confirm again
|
|
405
456
|
if (!booking.utcConfirmedAt && !partialOrder) {
|
|
457
|
+
const validatedCountry = validateCountry(R.pathOr('', ['country'], holder));
|
|
458
|
+
|
|
459
|
+
// Ensure required contact fields are present
|
|
460
|
+
const firstName = R.pathOr('', ['name'], holder);
|
|
461
|
+
const lastName = R.pathOr('', ['surname'], holder);
|
|
462
|
+
// Validate required fields
|
|
463
|
+
if (!firstName.trim() || !lastName.trim()) {
|
|
464
|
+
console.error('Customer validation failed: First and last name are required');
|
|
465
|
+
throw new Error('Customer validation failed: First and last name are required');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const fullName = `${firstName.trim()} ${lastName.trim()}`;
|
|
469
|
+
const emailAddress = R.pathOr(null, ['emailAddress'], holder);
|
|
470
|
+
const phoneNumber = R.pathOr('', ['phone'], holder);
|
|
471
|
+
|
|
472
|
+
// Basic email validation
|
|
473
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
474
|
+
if (emailAddress && !emailRegex.test(emailAddress)) {
|
|
475
|
+
console.error(`Customer validation failed: Invalid email format: ${emailAddress}`);
|
|
476
|
+
throw new Error('Customer validation failed: Invalid email address format');
|
|
477
|
+
}
|
|
406
478
|
const dataForConfirmBooking = {
|
|
407
479
|
contact: {
|
|
408
|
-
fullName
|
|
409
|
-
emailAddress
|
|
410
|
-
phoneNumber:
|
|
411
|
-
locales: R.path(['locales'], holder),
|
|
412
|
-
country:
|
|
480
|
+
fullName,
|
|
481
|
+
emailAddress,
|
|
482
|
+
phoneNumber: phoneNumber || '',
|
|
483
|
+
locales: R.path(['locales'], holder) || ['en'],
|
|
484
|
+
country: validatedCountry,
|
|
485
|
+
postalCode: R.path(['zipCode'], holder) || '',
|
|
413
486
|
},
|
|
414
487
|
notes,
|
|
415
488
|
resellerReference: reference,
|
|
416
|
-
settlementMethod
|
|
489
|
+
settlementMethod,
|
|
490
|
+
...(emailAddress ? { emailReceipt: true } : {}), // Send email receipt since we have a valid email address
|
|
417
491
|
};
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
booking = R.path(['data'], await axios({
|
|
495
|
+
method: 'post',
|
|
496
|
+
url: `${endpoint || this.endpoint}/bookings/${booking.uuid}/confirm`,
|
|
497
|
+
data: dataForConfirmBooking,
|
|
498
|
+
headers,
|
|
499
|
+
}));
|
|
500
|
+
} catch (error) {
|
|
501
|
+
console.error('Booking confirmation failed:', (error.response && error.response.data) ? error.response.data : error.message);
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
424
504
|
}
|
|
425
505
|
return ({
|
|
426
506
|
booking: await translateBooking({
|
|
@@ -623,8 +703,6 @@ class Plugin {
|
|
|
623
703
|
},
|
|
624
704
|
query: {
|
|
625
705
|
productId,
|
|
626
|
-
date,
|
|
627
|
-
dateFormat,
|
|
628
706
|
},
|
|
629
707
|
}) {
|
|
630
708
|
const headers = getHeaders({
|
|
@@ -647,6 +725,7 @@ class Plugin {
|
|
|
647
725
|
return 'long';
|
|
648
726
|
})(),
|
|
649
727
|
options: o.selectOptions,
|
|
728
|
+
isPerUnitItem: o.isPerUnitItem,
|
|
650
729
|
});
|
|
651
730
|
if (!productId) {
|
|
652
731
|
const allProducts = R.pathOr([], ['data'], await axios({
|
|
@@ -655,13 +734,15 @@ class Plugin {
|
|
|
655
734
|
headers,
|
|
656
735
|
}));
|
|
657
736
|
const allOptions = R.chain(R.propOr([], 'options'), allProducts);
|
|
658
|
-
const allUnits = R.chain(R.propOr([], 'units'), allOptions);
|
|
737
|
+
const allUnits = R.chain(R.propOr([], 'units'), allOptions).map(o => ({ ...o, isPerUnitItem: true }));
|
|
659
738
|
const allQuestions = R.call(R.compose(
|
|
660
739
|
R.uniqBy(R.prop('id')),
|
|
661
|
-
R.chain(
|
|
740
|
+
R.chain(obj => (obj.questions || []).map(q => ({
|
|
741
|
+
...q,
|
|
742
|
+
isPerUnitItem: Boolean(obj.isPerUnitItem),
|
|
743
|
+
}))),
|
|
662
744
|
R.filter(o => o.questions && o.questions.length),
|
|
663
745
|
), [...allOptions, ...allUnits]);
|
|
664
|
-
console.log(allQuestions);
|
|
665
746
|
return {
|
|
666
747
|
fields: [],
|
|
667
748
|
customFields: allQuestions.map(convertQToField),
|
|
@@ -677,11 +758,14 @@ class Plugin {
|
|
|
677
758
|
const allOptions = R.propOr([], 'options', product);
|
|
678
759
|
const allQuestions = R.call(R.compose(
|
|
679
760
|
R.uniqBy(R.prop('id')),
|
|
680
|
-
R.chain(
|
|
761
|
+
R.chain(obj => (obj.questions || []).map(q => ({
|
|
762
|
+
...q,
|
|
763
|
+
isPerUnitItem: Boolean(obj.isPerUnitItem),
|
|
764
|
+
}))),
|
|
681
765
|
R.filter(o => o.questions && o.questions.length),
|
|
682
766
|
), [
|
|
683
767
|
...allOptions,
|
|
684
|
-
...R.chain(R.propOr([], 'units'), allOptions),
|
|
768
|
+
...R.chain(R.propOr([], 'units'), allOptions).map(o => ({ ...o, isPerUnitItem: true })),
|
|
685
769
|
]);
|
|
686
770
|
if (!product) return { fields: [] };
|
|
687
771
|
return {
|
package/index.test.js
CHANGED
|
@@ -33,7 +33,7 @@ const app = new Plugin({
|
|
|
33
33
|
describe('search tests', () => {
|
|
34
34
|
let products;
|
|
35
35
|
let testProduct = {
|
|
36
|
-
productName: 'Edinburgh
|
|
36
|
+
productName: '[ED3D] Edinburgh 3 Day Pass',
|
|
37
37
|
};
|
|
38
38
|
const token = {
|
|
39
39
|
apiKey: process.env.ti2_ventrata_apiKey,
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ const resolvers = {
|
|
|
12
12
|
currency,
|
|
13
13
|
unitsWithQuantity,
|
|
14
14
|
jwtKey,
|
|
15
|
+
settlementMethods,
|
|
15
16
|
} = args;
|
|
16
17
|
if (!jwtKey) return null;
|
|
17
18
|
return jwt.sign({
|
|
@@ -19,6 +20,7 @@ const resolvers = {
|
|
|
19
20
|
optionId,
|
|
20
21
|
availabilityId: root.id,
|
|
21
22
|
currency,
|
|
23
|
+
settlementMethods,
|
|
22
24
|
unitItems: R.chain(u => new Array(u.quantity).fill(1).map(() => ({
|
|
23
25
|
unitId: u.unitId,
|
|
24
26
|
})), unitsWithQuantity),
|
package/resolvers/booking.js
CHANGED
|
@@ -35,6 +35,9 @@ const resolvers = {
|
|
|
35
35
|
fullName: R.pathOr('', ['contact', 'fullName'], root),
|
|
36
36
|
phoneNumber: R.pathOr('', ['contact', 'phoneNumber'], root),
|
|
37
37
|
emailAddress: R.pathOr('', ['contact', 'emailAddress'], root),
|
|
38
|
+
country: R.pathOr('', ['contact', 'country'], root),
|
|
39
|
+
locales: R.pathOr('', ['locales'], root),
|
|
40
|
+
postalCode: R.pathOr('', ['contact', 'postalCode'], root),
|
|
38
41
|
}),
|
|
39
42
|
notes: root => root.notes || '',
|
|
40
43
|
price: root => root.pricing,
|
package/resolvers/product.js
CHANGED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Country name to ISO 3166-1 alpha-2 code mapping
|
|
2
|
+
const countryNameToCode = {
|
|
3
|
+
// Major countries
|
|
4
|
+
'UNITED STATES': 'US',
|
|
5
|
+
'USA': 'US',
|
|
6
|
+
'AMERICA': 'US',
|
|
7
|
+
'UNITED STATES OF AMERICA': 'US',
|
|
8
|
+
'UNITED KINGDOM': 'GB',
|
|
9
|
+
'UK': 'GB',
|
|
10
|
+
'GREAT BRITAIN': 'GB',
|
|
11
|
+
'ENGLAND': 'GB',
|
|
12
|
+
'BRITAIN': 'GB',
|
|
13
|
+
'CANADA': 'CA',
|
|
14
|
+
'FRANCE': 'FR',
|
|
15
|
+
'GERMANY': 'DE',
|
|
16
|
+
'ITALY': 'IT',
|
|
17
|
+
'SPAIN': 'ES',
|
|
18
|
+
'AUSTRALIA': 'AU',
|
|
19
|
+
'NEW ZEALAND': 'NZ',
|
|
20
|
+
'JAPAN': 'JP',
|
|
21
|
+
'CHINA': 'CN',
|
|
22
|
+
'INDIA': 'IN',
|
|
23
|
+
'BRAZIL': 'BR',
|
|
24
|
+
'MEXICO': 'MX',
|
|
25
|
+
'RUSSIA': 'RU',
|
|
26
|
+
'SOUTH AFRICA': 'ZA',
|
|
27
|
+
'ARGENTINA': 'AR',
|
|
28
|
+
'CHILE': 'CL',
|
|
29
|
+
'COLOMBIA': 'CO',
|
|
30
|
+
'PERU': 'PE',
|
|
31
|
+
'VENEZUELA': 'VE',
|
|
32
|
+
'ECUADOR': 'EC',
|
|
33
|
+
'BOLIVIA': 'BO',
|
|
34
|
+
'PARAGUAY': 'PY',
|
|
35
|
+
'URUGUAY': 'UY',
|
|
36
|
+
'GUYANA': 'GY',
|
|
37
|
+
'SURINAME': 'SR',
|
|
38
|
+
'NETHERLANDS': 'NL',
|
|
39
|
+
'BELGIUM': 'BE',
|
|
40
|
+
'SWITZERLAND': 'CH',
|
|
41
|
+
'AUSTRIA': 'AT',
|
|
42
|
+
'PORTUGAL': 'PT',
|
|
43
|
+
'SWEDEN': 'SE',
|
|
44
|
+
'NORWAY': 'NO',
|
|
45
|
+
'DENMARK': 'DK',
|
|
46
|
+
'FINLAND': 'FI',
|
|
47
|
+
'ICELAND': 'IS',
|
|
48
|
+
'IRELAND': 'IE',
|
|
49
|
+
'POLAND': 'PL',
|
|
50
|
+
'CZECH REPUBLIC': 'CZ',
|
|
51
|
+
'SLOVAKIA': 'SK',
|
|
52
|
+
'HUNGARY': 'HU',
|
|
53
|
+
'ROMANIA': 'RO',
|
|
54
|
+
'BULGARIA': 'BG',
|
|
55
|
+
'CROATIA': 'HR',
|
|
56
|
+
'SLOVENIA': 'SI',
|
|
57
|
+
'SERBIA': 'RS',
|
|
58
|
+
'BOSNIA AND HERZEGOVINA': 'BA',
|
|
59
|
+
'MONTENEGRO': 'ME',
|
|
60
|
+
'ALBANIA': 'AL',
|
|
61
|
+
'MACEDONIA': 'MK',
|
|
62
|
+
'GREECE': 'GR',
|
|
63
|
+
'TURKEY': 'TR',
|
|
64
|
+
'CYPRUS': 'CY',
|
|
65
|
+
'MALTA': 'MT',
|
|
66
|
+
'LUXEMBOURG': 'LU',
|
|
67
|
+
'ESTONIA': 'EE',
|
|
68
|
+
'LATVIA': 'LV',
|
|
69
|
+
'LITHUANIA': 'LT',
|
|
70
|
+
'UKRAINE': 'UA',
|
|
71
|
+
'BELARUS': 'BY',
|
|
72
|
+
'MOLDOVA': 'MD',
|
|
73
|
+
'GEORGIA': 'GE',
|
|
74
|
+
'ARMENIA': 'AM',
|
|
75
|
+
'AZERBAIJAN': 'AZ',
|
|
76
|
+
'KAZAKHSTAN': 'KZ',
|
|
77
|
+
'UZBEKISTAN': 'UZ',
|
|
78
|
+
'TURKMENISTAN': 'TM',
|
|
79
|
+
'KYRGYZSTAN': 'KG',
|
|
80
|
+
'TAJIKISTAN': 'TJ',
|
|
81
|
+
'AFGHANISTAN': 'AF',
|
|
82
|
+
'PAKISTAN': 'PK',
|
|
83
|
+
'BANGLADESH': 'BD',
|
|
84
|
+
'SRI LANKA': 'LK',
|
|
85
|
+
'NEPAL': 'NP',
|
|
86
|
+
'BHUTAN': 'BT',
|
|
87
|
+
'MALDIVES': 'MV',
|
|
88
|
+
'MYANMAR': 'MM',
|
|
89
|
+
'THAILAND': 'TH',
|
|
90
|
+
'LAOS': 'LA',
|
|
91
|
+
'VIETNAM': 'VN',
|
|
92
|
+
'CAMBODIA': 'KH',
|
|
93
|
+
'MALAYSIA': 'MY',
|
|
94
|
+
'SINGAPORE': 'SG',
|
|
95
|
+
'INDONESIA': 'ID',
|
|
96
|
+
'PHILIPPINES': 'PH',
|
|
97
|
+
'BRUNEI': 'BN',
|
|
98
|
+
'TIMOR-LESTE': 'TL',
|
|
99
|
+
'SOUTH KOREA': 'KR',
|
|
100
|
+
'NORTH KOREA': 'KP',
|
|
101
|
+
'MONGOLIA': 'MN',
|
|
102
|
+
'TAIWAN': 'TW',
|
|
103
|
+
'HONG KONG': 'HK',
|
|
104
|
+
'MACAU': 'MO',
|
|
105
|
+
'ISRAEL': 'IL',
|
|
106
|
+
'PALESTINE': 'PS',
|
|
107
|
+
'JORDAN': 'JO',
|
|
108
|
+
'LEBANON': 'LB',
|
|
109
|
+
'SYRIA': 'SY',
|
|
110
|
+
'IRAQ': 'IQ',
|
|
111
|
+
'IRAN': 'IR',
|
|
112
|
+
'KUWAIT': 'KW',
|
|
113
|
+
'SAUDI ARABIA': 'SA',
|
|
114
|
+
'BAHRAIN': 'BH',
|
|
115
|
+
'QATAR': 'QA',
|
|
116
|
+
'UAE': 'AE',
|
|
117
|
+
'UNITED ARAB EMIRATES': 'AE',
|
|
118
|
+
'OMAN': 'OM',
|
|
119
|
+
'YEMEN': 'YE',
|
|
120
|
+
'EGYPT': 'EG',
|
|
121
|
+
'LIBYA': 'LY',
|
|
122
|
+
'TUNISIA': 'TN',
|
|
123
|
+
'ALGERIA': 'DZ',
|
|
124
|
+
'MOROCCO': 'MA',
|
|
125
|
+
'SUDAN': 'SD',
|
|
126
|
+
'SOUTH SUDAN': 'SS',
|
|
127
|
+
'ETHIOPIA': 'ET',
|
|
128
|
+
'ERITREA': 'ER',
|
|
129
|
+
'DJIBOUTI': 'DJ',
|
|
130
|
+
'SOMALIA': 'SO',
|
|
131
|
+
'KENYA': 'KE',
|
|
132
|
+
'UGANDA': 'UG',
|
|
133
|
+
'TANZANIA': 'TZ',
|
|
134
|
+
'RWANDA': 'RW',
|
|
135
|
+
'BURUNDI': 'BI',
|
|
136
|
+
'DEMOCRATIC REPUBLIC OF THE CONGO': 'CD',
|
|
137
|
+
'CONGO': 'CG',
|
|
138
|
+
'CENTRAL AFRICAN REPUBLIC': 'CF',
|
|
139
|
+
'CHAD': 'TD',
|
|
140
|
+
'CAMEROON': 'CM',
|
|
141
|
+
'EQUATORIAL GUINEA': 'GQ',
|
|
142
|
+
'GABON': 'GA',
|
|
143
|
+
'SAO TOME AND PRINCIPE': 'ST',
|
|
144
|
+
'NIGERIA': 'NG',
|
|
145
|
+
'NIGER': 'NE',
|
|
146
|
+
'MALI': 'ML',
|
|
147
|
+
'BURKINA FASO': 'BF',
|
|
148
|
+
'SENEGAL': 'SN',
|
|
149
|
+
'GAMBIA': 'GM',
|
|
150
|
+
'GUINEA-BISSAU': 'GW',
|
|
151
|
+
'GUINEA': 'GN',
|
|
152
|
+
'SIERRA LEONE': 'SL',
|
|
153
|
+
'LIBERIA': 'LR',
|
|
154
|
+
'IVORY COAST': 'CI',
|
|
155
|
+
'GHANA': 'GH',
|
|
156
|
+
'TOGO': 'TG',
|
|
157
|
+
'BENIN': 'BJ',
|
|
158
|
+
'MAURITANIA': 'MR',
|
|
159
|
+
'WESTERN SAHARA': 'EH',
|
|
160
|
+
'CAPE VERDE': 'CV',
|
|
161
|
+
'MADAGASCAR': 'MG',
|
|
162
|
+
'MAURITIUS': 'MU',
|
|
163
|
+
'SEYCHELLES': 'SC',
|
|
164
|
+
'COMOROS': 'KM',
|
|
165
|
+
'MAYOTTE': 'YT',
|
|
166
|
+
'REUNION': 'RE',
|
|
167
|
+
'BOTSWANA': 'BW',
|
|
168
|
+
'NAMIBIA': 'NA',
|
|
169
|
+
'ZAMBIA': 'ZM',
|
|
170
|
+
'ZIMBABWE': 'ZW',
|
|
171
|
+
'MALAWI': 'MW',
|
|
172
|
+
'MOZAMBIQUE': 'MZ',
|
|
173
|
+
'SWAZILAND': 'SZ',
|
|
174
|
+
'LESOTHO': 'LS',
|
|
175
|
+
'ANGOLA': 'AO',
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const validateCountry = countryInput => {
|
|
179
|
+
// Validate and format country code - ensure it's a valid ISO 3166-1 alpha-2 code
|
|
180
|
+
let validatedCountry = 'US'; // Default fallback
|
|
181
|
+
|
|
182
|
+
if (countryInput && typeof countryInput === 'string') {
|
|
183
|
+
const cleanInput = countryInput.trim().toUpperCase();
|
|
184
|
+
|
|
185
|
+
// First, check if it's already a valid 2-letter ISO code
|
|
186
|
+
if (cleanInput.length === 2 && /^[A-Z]{2}$/.test(cleanInput)) {
|
|
187
|
+
validatedCountry = cleanInput;
|
|
188
|
+
} else if (countryNameToCode[cleanInput]) {
|
|
189
|
+
// Then, try to convert country name to ISO code
|
|
190
|
+
validatedCountry = countryNameToCode[cleanInput];
|
|
191
|
+
} else {
|
|
192
|
+
// If it looks like a partial match, try to find it
|
|
193
|
+
const matchedEntry = Object.entries(countryNameToCode).find(([name]) =>
|
|
194
|
+
name.includes(cleanInput) || cleanInput.includes(name));
|
|
195
|
+
if (matchedEntry) {
|
|
196
|
+
const [, countryCode] = matchedEntry;
|
|
197
|
+
validatedCountry = countryCode;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return validatedCountry;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
validateCountry,
|
|
206
|
+
};
|