redsys-easy 3.1.0 → 4.0.2
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/README.md +94 -419
- package/lib/assets/card-brands.d.ts +26 -2
- package/lib/assets/countries.d.ts +29 -2
- package/lib/assets/currencies.d.ts +26 -2
- package/lib/assets/lang-codes.d.ts +29 -2
- package/lib/assets/transaction-types.d.ts +38 -3
- package/lib/client.d.ts +50 -0
- package/lib/crypto.d.ts +25 -0
- package/lib/errors.d.ts +61 -6
- package/lib/formatter-utils.d.ts +24 -0
- package/lib/formatters/input-params.d.ts +21 -0
- package/lib/formatters/output-params.d.ts +29 -0
- package/lib/formatters/types.d.ts +316 -0
- package/lib/index.d.ts +30 -56
- package/lib/index.esm.js +957 -746
- package/lib/index.js +990 -747
- package/lib/rest/3ds.d.ts +31 -0
- package/lib/rest/json-serialization.d.ts +2 -0
- package/lib/rest/json-sha256-signature.d.ts +3 -0
- package/lib/rest/json.d.ts +3 -0
- package/lib/rest/requests.d.ts +2 -0
- package/lib/soap/notification-serialization.d.ts +3 -0
- package/lib/soap/notification-signature.d.ts +3 -0
- package/lib/soap/notification.d.ts +4 -0
- package/lib/soap/utils.d.ts +43 -0
- package/lib/types/3ds-params.d.ts +345 -0
- package/lib/types/api.d.ts +69 -78
- package/lib/types/input-params.d.ts +176 -0
- package/lib/types/output-params.d.ts +158 -0
- package/lib/utils/codes.d.ts +13 -0
- package/lib/utils/misc.d.ts +24 -0
- package/package.json +63 -57
- package/CHANGELOG.md +0 -53
- package/lib/params-formatter.d.ts +0 -7
- package/lib/response-formatter.d.ts +0 -5
- package/lib/utils.d.ts +0 -39
package/lib/index.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var http = require('http');
|
|
5
6
|
var base64url = require('base64url');
|
|
6
|
-
var xmlParser = require('fast-xml-parser');
|
|
7
|
-
var soap = require('soap');
|
|
8
7
|
var crypto = require('crypto');
|
|
8
|
+
var fastXmlParser = require('fast-xml-parser');
|
|
9
|
+
var fetch = require('node-fetch');
|
|
10
|
+
var Decimal = require('decimal.js');
|
|
9
11
|
var dayjs = require('dayjs');
|
|
10
12
|
var timezone = require('dayjs/plugin/timezone');
|
|
11
13
|
var utc = require('dayjs/plugin/utc');
|
|
@@ -13,43 +15,13 @@ var utc = require('dayjs/plugin/utc');
|
|
|
13
15
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
14
16
|
|
|
15
17
|
var base64url__default = /*#__PURE__*/_interopDefaultLegacy(base64url);
|
|
16
|
-
var xmlParser__default = /*#__PURE__*/_interopDefaultLegacy(xmlParser);
|
|
17
18
|
var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
|
|
19
|
+
var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
|
|
20
|
+
var Decimal__default = /*#__PURE__*/_interopDefaultLegacy(Decimal);
|
|
18
21
|
var dayjs__default = /*#__PURE__*/_interopDefaultLegacy(dayjs);
|
|
19
22
|
var timezone__default = /*#__PURE__*/_interopDefaultLegacy(timezone);
|
|
20
23
|
var utc__default = /*#__PURE__*/_interopDefaultLegacy(utc);
|
|
21
24
|
|
|
22
|
-
class RedsysError extends Error {
|
|
23
|
-
constructor(message) {
|
|
24
|
-
super(message);
|
|
25
|
-
this.name = 'RedsysError';
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
class ValidationError extends RedsysError {
|
|
29
|
-
constructor(message, value, field) {
|
|
30
|
-
super(message);
|
|
31
|
-
this.name = 'RedsysValidationError';
|
|
32
|
-
this.value = value;
|
|
33
|
-
this.field = field;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
class ParseError extends RedsysError {
|
|
37
|
-
constructor(message, value, text) {
|
|
38
|
-
super(message);
|
|
39
|
-
this.name = 'RedsysParseError';
|
|
40
|
-
this.value = value;
|
|
41
|
-
this.text = text;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
class GatewayError extends RedsysError {
|
|
45
|
-
constructor(message, code, response) {
|
|
46
|
-
super(message);
|
|
47
|
-
this.name = 'RedsysParseError';
|
|
48
|
-
this.code = code;
|
|
49
|
-
this.response = response;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
25
|
const ALL_ERRORS = [
|
|
54
26
|
{ code: 9001, sisCode: 'SIS0001', text: 'Error Interno' },
|
|
55
27
|
{ code: 9002, sisCode: 'SIS0002', text: 'Error genérico' },
|
|
@@ -503,24 +475,151 @@ const RESPONSE_CODES = {
|
|
|
503
475
|
900: 'Transacción autorizada para devoluciones y confirmaciones.'
|
|
504
476
|
};
|
|
505
477
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
478
|
+
/**
|
|
479
|
+
* Returns the message corresponding to a response code, in spanish
|
|
480
|
+
*
|
|
481
|
+
* @public
|
|
482
|
+
*/
|
|
483
|
+
const getResponseCodeMessage = (code) => {
|
|
484
|
+
if (typeof code !== 'number' && typeof code !== 'string') {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const lookupNum = typeof code === 'string' ? Number.parseFloat(code.trim()) : code;
|
|
488
|
+
if (!Number.isInteger(lookupNum) || lookupNum < 0) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const msg = RESPONSE_CODES[lookupNum.toString()];
|
|
492
|
+
if ((msg == null || !msg) && lookupNum < 100) {
|
|
493
|
+
return 'Transacción autorizada para pagos y preautorizaciones';
|
|
494
|
+
}
|
|
495
|
+
return msg;
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* Returns the message corresponding to a gateway error code
|
|
499
|
+
*
|
|
500
|
+
* @public
|
|
501
|
+
*/
|
|
502
|
+
const getSISErrorCodeMessage = (code) => {
|
|
503
|
+
if (!code || typeof code !== 'string') {
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
return SIS_ERROR_CODES[code.trim()];
|
|
507
|
+
};
|
|
508
|
+
const getHTTPErrorCodeMessage = (code) => {
|
|
509
|
+
return http.STATUS_CODES[code];
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Error superclass for all redsys-easy specific errors
|
|
514
|
+
*
|
|
515
|
+
* @public
|
|
516
|
+
*/
|
|
517
|
+
class RedsysError extends Error {
|
|
518
|
+
constructor(message) {
|
|
519
|
+
super(message);
|
|
520
|
+
this.name = 'RedsysError';
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Invalid input provided
|
|
525
|
+
*
|
|
526
|
+
* @public
|
|
527
|
+
*/
|
|
528
|
+
class ValidationError extends RedsysError {
|
|
529
|
+
constructor(message, parameters) {
|
|
530
|
+
super(message);
|
|
531
|
+
this.name = 'RedsysValidationError';
|
|
532
|
+
this.parameters = parameters;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Response cannot be parsed
|
|
537
|
+
*
|
|
538
|
+
* @public
|
|
539
|
+
*/
|
|
540
|
+
class ParseError extends RedsysError {
|
|
541
|
+
constructor(message, value, location) {
|
|
542
|
+
super(message);
|
|
543
|
+
this.name = 'RedsysParseError';
|
|
544
|
+
this.value = value;
|
|
545
|
+
this.location = location;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* HTTP Error
|
|
550
|
+
*
|
|
551
|
+
* @public
|
|
552
|
+
*/
|
|
553
|
+
class HTTPError extends RedsysError {
|
|
554
|
+
constructor({ message, code, response }) {
|
|
555
|
+
const codeDescription = getHTTPErrorCodeMessage(code);
|
|
556
|
+
const codeInfo = code !== undefined
|
|
557
|
+
? ` ${code}${codeDescription != null ? `: ${codeDescription}` : ''}`
|
|
558
|
+
: '';
|
|
559
|
+
const augmentationLine = message != null && message ? `\n${message}` : '';
|
|
560
|
+
const augmentedMessage = `HTTP error${codeInfo}${augmentationLine}`;
|
|
561
|
+
super(augmentedMessage);
|
|
562
|
+
this.name = 'RedsysHTTPError';
|
|
563
|
+
this.code = code;
|
|
564
|
+
this.response = response;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Request could not be processed by Redsys
|
|
569
|
+
*
|
|
570
|
+
* @public
|
|
571
|
+
*/
|
|
572
|
+
class GatewayError extends RedsysError {
|
|
573
|
+
constructor({ message, code, response }) {
|
|
574
|
+
const codeDescription = getSISErrorCodeMessage(code);
|
|
575
|
+
const codeInfo = code !== undefined
|
|
576
|
+
? ` ${code}${codeDescription != null ? `: ${codeDescription}` : ''}`
|
|
577
|
+
: '';
|
|
578
|
+
const augmentationLine = message != null && message ? `\n${message}` : '';
|
|
579
|
+
const augmentedMessage = `Gateway error${codeInfo}${augmentationLine}`;
|
|
580
|
+
super(augmentedMessage);
|
|
581
|
+
this.name = 'RedsysGatewayError';
|
|
582
|
+
this.code = code;
|
|
583
|
+
this.response = response;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Response contained an error code
|
|
588
|
+
*
|
|
589
|
+
* @public
|
|
590
|
+
*/
|
|
591
|
+
class ResponseError extends RedsysError {
|
|
592
|
+
constructor({ message, code, response }) {
|
|
593
|
+
const codeDescription = getResponseCodeMessage(code);
|
|
594
|
+
const codeInfo = code !== undefined
|
|
595
|
+
? ` ${code}${codeDescription != null ? `: ${codeDescription}` : ''}`
|
|
596
|
+
: '';
|
|
597
|
+
const augmentationLine = message != null && message ? `\n${message}` : '';
|
|
598
|
+
const augmentedMessage = `Response error${codeInfo}${augmentationLine}`;
|
|
599
|
+
super(augmentedMessage);
|
|
600
|
+
this.name = 'RedsysResponseError';
|
|
601
|
+
this.code = code;
|
|
602
|
+
this.response = response;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const deserializeJSONMerchantParams = (strPayload) => {
|
|
607
|
+
if (!strPayload) {
|
|
608
|
+
throw new ParseError('Payload is empty');
|
|
609
|
+
}
|
|
610
|
+
if (typeof strPayload !== 'string') {
|
|
611
|
+
throw new ParseError('Payload must be a base-64 encoded string');
|
|
612
|
+
}
|
|
613
|
+
const payload = JSON.parse(decodeURIComponent(Buffer.from(strPayload, 'base64').toString('utf8')));
|
|
614
|
+
if (typeof payload !== 'object' || payload == null) {
|
|
615
|
+
throw new ParseError('Cannot parse notification payload');
|
|
616
|
+
}
|
|
617
|
+
return payload;
|
|
618
|
+
};
|
|
619
|
+
const serializeJSONMerchantParams = (requestParams) => {
|
|
620
|
+
// Official redsys docs escape "/" because they use php json_encode. I think
|
|
621
|
+
// this is wrong and it also works without escaping.
|
|
622
|
+
return Buffer.from(JSON.stringify(requestParams), 'utf8').toString('base64');
|
|
524
623
|
};
|
|
525
624
|
|
|
526
625
|
/**
|
|
@@ -528,9 +627,8 @@ const TRANSACTION_TYPES = {
|
|
|
528
627
|
*
|
|
529
628
|
* Rounds up the buffer length to the next block and uses 0 as padding.
|
|
530
629
|
*
|
|
531
|
-
* @params
|
|
532
|
-
* @params
|
|
533
|
-
* @return {buffer}
|
|
630
|
+
* @params buf - Input buffer
|
|
631
|
+
* @params blocksize - Size of block
|
|
534
632
|
*/
|
|
535
633
|
const zeroPad = (buf, blocksize) => {
|
|
536
634
|
const pad = Buffer.alloc((blocksize - (buf.length % blocksize)) % blocksize, 0);
|
|
@@ -539,9 +637,8 @@ const zeroPad = (buf, blocksize) => {
|
|
|
539
637
|
/**
|
|
540
638
|
* Encrypt a message using 3DES
|
|
541
639
|
*
|
|
542
|
-
* @params
|
|
543
|
-
* @params
|
|
544
|
-
* @return {buffer}
|
|
640
|
+
* @params key - Key to encrypt message
|
|
641
|
+
* @params message - Message to be encrypted
|
|
545
642
|
*/
|
|
546
643
|
const encrypt3DES = (key, message) => {
|
|
547
644
|
const keyBuf = Buffer.from(key, 'base64');
|
|
@@ -556,34 +653,353 @@ const encrypt3DES = (key, message) => {
|
|
|
556
653
|
const maxLength = Math.ceil(messageBuf.length / 8) * 8;
|
|
557
654
|
return encryptedBuf.slice(0, maxLength);
|
|
558
655
|
};
|
|
656
|
+
/**
|
|
657
|
+
* Compute HMAC_SHA256_V1 signature
|
|
658
|
+
*
|
|
659
|
+
* @params merchantKey - Key to encrypt message
|
|
660
|
+
* @params order - Order number
|
|
661
|
+
* @params params - Payload to sign
|
|
662
|
+
*/
|
|
559
663
|
const sha256Sign = (merchantKey, order, params) => {
|
|
560
664
|
const orderKeyBuf = encrypt3DES(merchantKey, order);
|
|
561
665
|
return crypto__default['default'].createHmac('sha256', orderKeyBuf).update(params).digest('base64');
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Discrete uniform distribution with domain (0 to *max*)
|
|
670
|
+
*/
|
|
671
|
+
const drawPositiveDiscreteUniform = (max) => {
|
|
672
|
+
return Math.floor(Math.random() * (max + 1));
|
|
562
673
|
};
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
674
|
+
const alphanumeric = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
675
|
+
/**
|
|
676
|
+
* Generates a random order ID following redsys requirements
|
|
677
|
+
*
|
|
678
|
+
* @public
|
|
679
|
+
*/
|
|
680
|
+
const randomTransactionId = () => {
|
|
681
|
+
// Random 4 digit number between 0 and 9999
|
|
682
|
+
const num = drawPositiveDiscreteUniform(9999);
|
|
683
|
+
const numStr = num.toString().padStart(4, '0');
|
|
684
|
+
// Random 8 letter alphanumeric sequence
|
|
685
|
+
const alphanum = new Array(8);
|
|
686
|
+
for (let idx = 0; idx < 8; idx++) {
|
|
687
|
+
alphanum[idx] = alphanumeric[drawPositiveDiscreteUniform(alphanumeric.length - 1)];
|
|
566
688
|
}
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
689
|
+
const alphanumStr = alphanum.join('');
|
|
690
|
+
return `${numStr}${alphanumStr}`;
|
|
691
|
+
};
|
|
692
|
+
const extractAndAssertOrderFromRequestParams = (requestParams) => {
|
|
693
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing
|
|
694
|
+
const order = requestParams['DS_MERCHANT_ORDER'] || requestParams['Ds_Merchant_Order'];
|
|
695
|
+
if (typeof order !== 'string' || !order) {
|
|
696
|
+
throw new ParseError('Missing order number');
|
|
570
697
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
698
|
+
return order;
|
|
699
|
+
};
|
|
700
|
+
const extractAndAssertOrderFromResponseParams = (responseParams) => {
|
|
701
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing
|
|
702
|
+
const order = responseParams['Ds_Order'] || responseParams['DS_ORDER'];
|
|
703
|
+
if (typeof order !== 'string' || !order) {
|
|
704
|
+
throw new ParseError('Missing order number');
|
|
574
705
|
}
|
|
575
|
-
return
|
|
706
|
+
return order;
|
|
576
707
|
};
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
708
|
+
/**
|
|
709
|
+
* Indicates if a response code signals success
|
|
710
|
+
*
|
|
711
|
+
* @public
|
|
712
|
+
*/
|
|
713
|
+
const isResponseCodeOk = (responseCode) => {
|
|
714
|
+
const numResCode = Number.parseInt(responseCode);
|
|
715
|
+
return Number.isFinite(numResCode) && ((numResCode >= 0 && numResCode < 100) ||
|
|
716
|
+
numResCode === 400 || // Transaction cancellation authorized
|
|
717
|
+
numResCode === 900 // Refund authorized
|
|
718
|
+
);
|
|
719
|
+
};
|
|
720
|
+
/**
|
|
721
|
+
* Asserts that response has a code that indicates success
|
|
722
|
+
*
|
|
723
|
+
* @public
|
|
724
|
+
*/
|
|
725
|
+
const assertSuccessfulResponseCode = (responseParams) => {
|
|
726
|
+
const { Ds_Response: resCode } = responseParams;
|
|
727
|
+
if (resCode == null) {
|
|
728
|
+
throw new ParseError('Missing response code');
|
|
580
729
|
}
|
|
581
|
-
|
|
730
|
+
if (!isResponseCodeOk(resCode)) {
|
|
731
|
+
throw new ResponseError({ code: Number.parseInt(resCode), response: responseParams });
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
const isStringNotEmpty = (str) => str != null && str.length > 0;
|
|
735
|
+
|
|
736
|
+
const sha256VerifyJSONResponse = (merchantKey, response, responseParams) => {
|
|
737
|
+
if (response.Ds_SignatureVersion !== 'HMAC_SHA256_V1') {
|
|
738
|
+
throw new RedsysError(`Unknown signature version: ${response.Ds_SignatureVersion}`);
|
|
739
|
+
}
|
|
740
|
+
const order = extractAndAssertOrderFromResponseParams(responseParams);
|
|
741
|
+
const signature = response.Ds_Signature;
|
|
742
|
+
// Base64url with padding, only substitutes + and /
|
|
743
|
+
const expSignature = sha256Sign(merchantKey, order, response.Ds_MerchantParameters);
|
|
744
|
+
// Comparing different base64 encodings is messy. Make it foolproof by comparing buffers.
|
|
745
|
+
if (!signature || !Buffer.from(expSignature, 'base64').equals(base64url__default['default'].toBuffer(signature))) {
|
|
746
|
+
throw new ParseError('Invalid signature');
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
const sha256SignJSONRequest = (merchantKey, serializedParams, requestParams) => {
|
|
750
|
+
const order = extractAndAssertOrderFromRequestParams(requestParams);
|
|
751
|
+
const signature = sha256Sign(merchantKey, order, serializedParams);
|
|
752
|
+
return {
|
|
753
|
+
Ds_SignatureVersion: 'HMAC_SHA256_V1',
|
|
754
|
+
Ds_MerchantParameters: serializedParams,
|
|
755
|
+
Ds_Signature: signature
|
|
756
|
+
};
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const deserializeAndVerifyJSONResponse = (merchantKey, response) => {
|
|
760
|
+
const params = deserializeJSONMerchantParams(response.Ds_MerchantParameters);
|
|
761
|
+
sha256VerifyJSONResponse(merchantKey, response, params);
|
|
762
|
+
return params;
|
|
763
|
+
};
|
|
764
|
+
const serializeAndSignJSONRequest = (merchantKey, requestParams) => {
|
|
765
|
+
const serializedParams = serializeJSONMerchantParams(requestParams);
|
|
766
|
+
return sha256SignJSONRequest(merchantKey, serializedParams, requestParams);
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
const serializeSoapNotificationResponse = ({ allow: allowOperation }) => {
|
|
770
|
+
const answer = allowOperation ? 'OK' : 'KO';
|
|
771
|
+
return `<Response Ds_Version="0.0"><Ds_Response_Merchant>${answer}</Ds_Response_Merchant></Response>`;
|
|
772
|
+
};
|
|
773
|
+
const xmlParser = new fastXmlParser.XMLParser({ parseTagValue: false });
|
|
774
|
+
const deserializeSoapNotification = (xml) => {
|
|
775
|
+
return xmlParser.parse(xml).Message;
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
const verifySoapNotification = (merchantKey, xml, msg) => {
|
|
779
|
+
const startToken = '<Request';
|
|
780
|
+
const endToken = '</Request>';
|
|
781
|
+
const startPos = xml.indexOf(startToken);
|
|
782
|
+
const endPos = xml.indexOf(endToken);
|
|
783
|
+
if (startPos < 0 || endPos < 0 || startPos > endPos) {
|
|
784
|
+
throw new ParseError('Cannot find payload of SOAP notification Request', xml);
|
|
785
|
+
}
|
|
786
|
+
const signedStr = xml.slice(startPos, endPos + endToken.length);
|
|
787
|
+
const signature = msg.Signature;
|
|
788
|
+
const order = msg.Request.Ds_Order;
|
|
789
|
+
if (order == null || !order) {
|
|
790
|
+
throw new ParseError('Invalid notification response');
|
|
791
|
+
}
|
|
792
|
+
const expSignature = sha256Sign(merchantKey, order, signedStr);
|
|
793
|
+
if (!signature || signature !== expSignature) {
|
|
794
|
+
throw new ParseError('Invalid signature', signature, xml);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
const signSoapNotificationResponse = (merchantKey, serializedResponse, response) => {
|
|
798
|
+
const signature = sha256Sign(merchantKey, response.order, serializedResponse);
|
|
799
|
+
return `<Message>${serializedResponse}<Signature>${signature}</Signature></Message>`;
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
const serializeAndSignSoapNotificationResponse = (merchantKey, response) => {
|
|
803
|
+
const serializedResponse = serializeSoapNotificationResponse(response);
|
|
804
|
+
return signSoapNotificationResponse(merchantKey, serializedResponse, response);
|
|
805
|
+
};
|
|
806
|
+
const deserializeAndVerifySoapNotification = (merchantKey, xml) => {
|
|
807
|
+
const msg = deserializeSoapNotification(xml);
|
|
808
|
+
verifySoapNotification(merchantKey, xml, msg);
|
|
809
|
+
// Return only parameters. Signature is useless after verification
|
|
810
|
+
return msg.Request;
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
const jsonRequest = async (url, merchantKey, rawRequestParams) => {
|
|
814
|
+
const payload = serializeAndSignJSONRequest(merchantKey, rawRequestParams);
|
|
815
|
+
const response = await fetch__default['default'](url, {
|
|
816
|
+
method: 'POST',
|
|
817
|
+
headers: {
|
|
818
|
+
'Content-Type': 'application/json'
|
|
819
|
+
},
|
|
820
|
+
body: JSON.stringify(payload)
|
|
821
|
+
});
|
|
822
|
+
const responseData = await response.json();
|
|
823
|
+
if (!response.ok) {
|
|
824
|
+
throw new HTTPError({
|
|
825
|
+
code: response.status,
|
|
826
|
+
response: responseData
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
if ('errorCode' in responseData) {
|
|
830
|
+
throw new GatewayError({
|
|
831
|
+
code: responseData.errorCode,
|
|
832
|
+
response: responseData
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
return deserializeAndVerifyJSONResponse(merchantKey, responseData);
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Redsys production urls
|
|
840
|
+
*
|
|
841
|
+
* @public
|
|
842
|
+
*/
|
|
843
|
+
const PRODUCTION_URLS = {
|
|
844
|
+
redirect: 'https://sis.redsys.es/sis/realizarPago',
|
|
845
|
+
restTrataPeticion: 'https://sis.redsys.es/sis/rest/trataPeticionREST',
|
|
846
|
+
restIniciaPeticion: 'https://sis.redsys.es/sis/rest/iniciaPeticionREST'
|
|
847
|
+
};
|
|
848
|
+
/**
|
|
849
|
+
* Redsys sandbox urls
|
|
850
|
+
*
|
|
851
|
+
* @public
|
|
852
|
+
*/
|
|
853
|
+
const SANDBOX_URLS = {
|
|
854
|
+
redirect: 'https://sis-t.redsys.es:25443/sis/realizarPago',
|
|
855
|
+
restTrataPeticion: 'https://sis-t.redsys.es:25443/sis/rest/trataPeticionREST',
|
|
856
|
+
restIniciaPeticion: 'https://sis-t.redsys.es:25443/sis/rest/iniciaPeticionREST'
|
|
857
|
+
};
|
|
858
|
+
/**
|
|
859
|
+
* Creates Redsys API functions
|
|
860
|
+
*
|
|
861
|
+
* @remarks
|
|
862
|
+
* These API functions are anonymous and can be wrapped by formatters and processors
|
|
863
|
+
*
|
|
864
|
+
* @public
|
|
865
|
+
*/
|
|
866
|
+
const createRedsysAPI = (config) => {
|
|
867
|
+
if (!config.secretKey || typeof config.secretKey !== 'string') {
|
|
868
|
+
throw new RedsysError('A secretKey key must be provided');
|
|
869
|
+
}
|
|
870
|
+
if (typeof config.urls !== 'object' || config.urls == null ||
|
|
871
|
+
!config.urls.restIniciaPeticion || !config.urls.restTrataPeticion ||
|
|
872
|
+
!config.urls.redirect) {
|
|
873
|
+
throw new RedsysError('URLs must be provided');
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Sends a iniciaPeticion request using REST interface
|
|
877
|
+
*/
|
|
878
|
+
const restIniciaPeticion = async (paramsInput) => {
|
|
879
|
+
return await jsonRequest(config.urls.restIniciaPeticion, config.secretKey, paramsInput);
|
|
880
|
+
};
|
|
881
|
+
/**
|
|
882
|
+
* Sends a trataPeticion request using REST interface
|
|
883
|
+
*/
|
|
884
|
+
const restTrataPeticion = async (paramsInput) => {
|
|
885
|
+
const result = await jsonRequest(config.urls.restTrataPeticion, config.secretKey, paramsInput);
|
|
886
|
+
return result;
|
|
887
|
+
};
|
|
888
|
+
/**
|
|
889
|
+
* Creates the parameters needed for a redirect form
|
|
890
|
+
*/
|
|
891
|
+
const createRedirectForm = (paramsInput) => {
|
|
892
|
+
const body = serializeAndSignJSONRequest(config.secretKey, paramsInput);
|
|
893
|
+
return {
|
|
894
|
+
url: config.urls.redirect,
|
|
895
|
+
body
|
|
896
|
+
};
|
|
897
|
+
};
|
|
898
|
+
/**
|
|
899
|
+
* Processes a JSON REST notification
|
|
900
|
+
*/
|
|
901
|
+
const processRestNotification = (
|
|
902
|
+
/** Body of JSON notification, as a POJO (Plain Old Javascript Object) */
|
|
903
|
+
body) => {
|
|
904
|
+
// A notification can't contain a gateway error, it didn't initiate the request
|
|
905
|
+
return deserializeAndVerifyJSONResponse(config.secretKey, body);
|
|
906
|
+
};
|
|
907
|
+
/**
|
|
908
|
+
* Parses and verifies the body of a SOAP notification
|
|
909
|
+
*/
|
|
910
|
+
const processSoapNotification = (
|
|
911
|
+
/** SOAP notification as a XML string */
|
|
912
|
+
xml) => {
|
|
913
|
+
return deserializeAndVerifySoapNotification(config.secretKey, xml);
|
|
914
|
+
};
|
|
915
|
+
/**
|
|
916
|
+
* Creates an answer for a SOAP notification, serializes and signs it
|
|
917
|
+
*/
|
|
918
|
+
const createSoapNotificationAnswer = (
|
|
919
|
+
/** Order identifier */
|
|
920
|
+
order,
|
|
921
|
+
/** Indicates if the payment is allowed to proceed */
|
|
922
|
+
allow) => {
|
|
923
|
+
return serializeAndSignSoapNotificationResponse(config.secretKey, {
|
|
924
|
+
order,
|
|
925
|
+
allow
|
|
926
|
+
});
|
|
927
|
+
};
|
|
928
|
+
return {
|
|
929
|
+
restIniciaPeticion,
|
|
930
|
+
restTrataPeticion,
|
|
931
|
+
createRedirectForm,
|
|
932
|
+
processRestNotification,
|
|
933
|
+
processSoapNotification,
|
|
934
|
+
createSoapNotificationAnswer
|
|
935
|
+
};
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
/*
|
|
939
|
+
* 3DS method form
|
|
940
|
+
*/
|
|
941
|
+
/**
|
|
942
|
+
* Creates parameters for a 3DS method form
|
|
943
|
+
*
|
|
944
|
+
* @public
|
|
945
|
+
*/
|
|
946
|
+
const create3DSMethodForm = (emv3dsParams, notificationURL) => {
|
|
947
|
+
const methodData = {
|
|
948
|
+
threeDSServerTransID: emv3dsParams.threeDSServerTransID,
|
|
949
|
+
threeDSMethodNotificationURL: notificationURL
|
|
950
|
+
};
|
|
951
|
+
return {
|
|
952
|
+
url: emv3dsParams.threeDSMethodURL,
|
|
953
|
+
body: {
|
|
954
|
+
threeDSMethodData: base64url__default['default'].encode(JSON.stringify(methodData))
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
};
|
|
958
|
+
/**
|
|
959
|
+
* Creates parameters for a 3DS v1 challenge form
|
|
960
|
+
*
|
|
961
|
+
* @public
|
|
962
|
+
*/
|
|
963
|
+
const create3DSv1ChallengeForm = (emv3dsV1Challenge, challengeNotificationUrl) => {
|
|
964
|
+
return {
|
|
965
|
+
url: emv3dsV1Challenge.acsURL,
|
|
966
|
+
body: {
|
|
967
|
+
PaReq: emv3dsV1Challenge.PAReq,
|
|
968
|
+
MD: emv3dsV1Challenge.MD,
|
|
969
|
+
TermUrl: challengeNotificationUrl
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
};
|
|
973
|
+
/**
|
|
974
|
+
* Creates parameters for a 3DS v2 challenge form
|
|
975
|
+
*
|
|
976
|
+
* @public
|
|
977
|
+
*/
|
|
978
|
+
const create3DSv2ChallengeForm = (emv3dsV2Challenge) => {
|
|
979
|
+
return {
|
|
980
|
+
url: emv3dsV2Challenge.acsURL,
|
|
981
|
+
body: {
|
|
982
|
+
creq: emv3dsV2Challenge.creq
|
|
983
|
+
}
|
|
984
|
+
};
|
|
985
|
+
};
|
|
986
|
+
/**
|
|
987
|
+
* Deserialize threeDSMethodData
|
|
988
|
+
*
|
|
989
|
+
* @public
|
|
990
|
+
*/
|
|
991
|
+
const deserializeThreeDSMethodData = (threeDSMethodData) => {
|
|
992
|
+
return JSON.parse(base64url__default['default'].decode(threeDSMethodData));
|
|
582
993
|
};
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
994
|
+
/**
|
|
995
|
+
* Deserialize `cres` field of a 3DS v2 challenge
|
|
996
|
+
*
|
|
997
|
+
* @public
|
|
998
|
+
*/
|
|
999
|
+
const deserializeCres = (cres) => {
|
|
1000
|
+
return JSON.parse(Buffer.from(cres, 'base64').toString('utf8'));
|
|
1001
|
+
};
|
|
1002
|
+
|
|
587
1003
|
const unescapeXML = (str) => {
|
|
588
1004
|
const xml = str.replace(/&(lt|#60|gt|#62|quot|#34|amp|#38|apos|#39);/g, (_match, p1) => {
|
|
589
1005
|
switch (p1) {
|
|
@@ -627,6 +1043,11 @@ const escapeXML = (str) => {
|
|
|
627
1043
|
});
|
|
628
1044
|
return xml;
|
|
629
1045
|
};
|
|
1046
|
+
/**
|
|
1047
|
+
* Detects the soap version of a HTTP request
|
|
1048
|
+
*
|
|
1049
|
+
* @public
|
|
1050
|
+
*/
|
|
630
1051
|
const detectSoapVersion = (req) => {
|
|
631
1052
|
var _a;
|
|
632
1053
|
const contentType = (_a = req.headers) === null || _a === void 0 ? void 0 : _a['Content-Type'];
|
|
@@ -648,6 +1069,14 @@ const detectSoapVersion = (req) => {
|
|
|
648
1069
|
}
|
|
649
1070
|
throw new ParseError('Not a valid SOAP request', req);
|
|
650
1071
|
};
|
|
1072
|
+
/**
|
|
1073
|
+
* Extracts the body of a SOAP notification from the body of the HTTP request
|
|
1074
|
+
*
|
|
1075
|
+
* @remarks
|
|
1076
|
+
* It does not understand SOAP or use WSDL, the implementation is a little bit hacky
|
|
1077
|
+
*
|
|
1078
|
+
* @public
|
|
1079
|
+
*/
|
|
651
1080
|
const mimicSoapNotificationReceiver = (xml) => {
|
|
652
1081
|
const regex = /<(?:\w+:)?XML(?: [^<>]+)?>([^<>]+)<\/(?:\w+:)?XML>/;
|
|
653
1082
|
const matchArray = regex.exec(xml);
|
|
@@ -658,73 +1087,37 @@ const mimicSoapNotificationReceiver = (xml) => {
|
|
|
658
1087
|
// wsdl defines XML tag as a string. It cannot be a CDATA (which is a XML element).
|
|
659
1088
|
return unescapeXML(xmlContent);
|
|
660
1089
|
};
|
|
1090
|
+
/**
|
|
1091
|
+
* Creates the body of a HTTP response as SOAP 1.1 from the body of a SOAP notification response
|
|
1092
|
+
*
|
|
1093
|
+
* @remarks
|
|
1094
|
+
* It does not understand SOAP or use WSDL, the implementation is a little bit hacky
|
|
1095
|
+
*
|
|
1096
|
+
* @public
|
|
1097
|
+
*/
|
|
661
1098
|
const mimicSoap11NotificationResponse = (answer) => {
|
|
662
1099
|
const escapedAnswer = escapeXML(answer);
|
|
663
1100
|
return `<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="https://sis.sermepa.es/sis/InotificacionSIS.wsdl" xmlns:types="https://sis.sermepa.es/sis/InotificacionSIS.wsdl/encodedTypes" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><q2:procesaNotificacionSISResponse xmlns:q2="InotificacionSIS"><return xsi:type="xsd:string">${escapedAnswer}</return></q2:procesaNotificacionSISResponse></soap:Body></soap:Envelope>`;
|
|
664
1101
|
};
|
|
665
|
-
|
|
1102
|
+
/**
|
|
1103
|
+
* Creates the body of a HTTP response as SOAP 1.2 from the body of a SOAP notification response
|
|
1104
|
+
*
|
|
1105
|
+
* @remarks
|
|
1106
|
+
* It does not understand SOAP or use WSDL, the implementation is a little bit hacky
|
|
1107
|
+
*
|
|
1108
|
+
* @public
|
|
1109
|
+
*/
|
|
1110
|
+
const mimicSoap12NotificationResponse = (answer) => {
|
|
666
1111
|
const escapedAnswer = escapeXML(answer);
|
|
667
1112
|
return `<?xml version="1.0" encoding="utf-8"?><soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://www.w3.org/2003/05/soap-encoding" xmlns:tns="https://sis.sermepa.es/sis/InotificacionSIS.wsdl" xmlns:types="https://sis.sermepa.es/sis/InotificacionSIS.wsdl/encodedTypes" xmlns:rpc="http://www.w3.org/2003/05/soap-rpc" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"><soap12:Body soap12:encodingStyle="http://www.w3.org/2003/05/soap-encoding"><q4:procesaNotificacionSISResponse xmlns:q4="InotificacionSIS"><rpc:result xmlns="">return</rpc:result><return xsi:type="xsd:string">${escapedAnswer}</return></q4:procesaNotificacionSISResponse></soap12:Body></soap12:Envelope>`;
|
|
668
|
-
};
|
|
669
|
-
// Range: 0 to (top -1)
|
|
670
|
-
const randomInt = (top) => {
|
|
671
|
-
return Math.floor(Math.random() * Math.floor(top));
|
|
672
|
-
};
|
|
673
|
-
const alphanumeric = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
674
|
-
const randomTransactionId = () => {
|
|
675
|
-
// Random 4 digit number between 0 and 9999
|
|
676
|
-
const num = randomInt(10000);
|
|
677
|
-
const numStr = num.toString().padStart(4, '0');
|
|
678
|
-
// Random 8 letter alphanumeric sequence
|
|
679
|
-
const alphanum = new Array(8);
|
|
680
|
-
for (let idx = 0; idx < 8; idx++) {
|
|
681
|
-
alphanum[idx] = alphanumeric[randomInt(alphanumeric.length)];
|
|
682
|
-
}
|
|
683
|
-
const alphanumStr = alphanum.join('');
|
|
684
|
-
return `${numStr}${alphanumStr}`;
|
|
685
1113
|
};
|
|
686
1114
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
de: '5',
|
|
694
|
-
nl: '6',
|
|
695
|
-
it: '7',
|
|
696
|
-
sv: '8',
|
|
697
|
-
pt: '9',
|
|
698
|
-
pl: '11',
|
|
699
|
-
gl: '12',
|
|
700
|
-
eu: '13',
|
|
701
|
-
// Redsys ha utilizado ISO 3166-1(pais) como codigo de idioma
|
|
702
|
-
bg: '100',
|
|
703
|
-
zh: '156',
|
|
704
|
-
hr: '191',
|
|
705
|
-
cs: '203',
|
|
706
|
-
da: '208',
|
|
707
|
-
et: '233',
|
|
708
|
-
fi: '246',
|
|
709
|
-
el: '300',
|
|
710
|
-
hu: '348',
|
|
711
|
-
ja: '392',
|
|
712
|
-
lv: '428',
|
|
713
|
-
lt: '440',
|
|
714
|
-
mt: '470',
|
|
715
|
-
ro: '642',
|
|
716
|
-
ru: '643',
|
|
717
|
-
sk: '703',
|
|
718
|
-
sl: '705',
|
|
719
|
-
tr: '792'
|
|
720
|
-
};
|
|
721
|
-
const REV_LANGUAGES = {};
|
|
722
|
-
for (const [key, value] of Object.entries(LANGUAGES)) {
|
|
723
|
-
REV_LANGUAGES[value] = key;
|
|
724
|
-
}
|
|
725
|
-
REV_LANGUAGES['10'] = 'ca';
|
|
726
|
-
|
|
727
|
-
// ISO 4217
|
|
1115
|
+
/**
|
|
1116
|
+
* Maps a ISO 4217 code to a ISO 4217 currency number and decimal positions
|
|
1117
|
+
*
|
|
1118
|
+
* @public
|
|
1119
|
+
*/
|
|
1120
|
+
// The Record key makes sure that all members of the key union type are defined.
|
|
728
1121
|
const CURRENCIES = {
|
|
729
1122
|
ALL: { num: '8', decimals: 2 },
|
|
730
1123
|
DZD: { num: '12', decimals: 2 },
|
|
@@ -938,6 +1331,15 @@ const CURRENCIES = {
|
|
|
938
1331
|
BRL: { num: '986', decimals: 2 }
|
|
939
1332
|
// ZAL: { num: '991', decimals: }
|
|
940
1333
|
};
|
|
1334
|
+
/**
|
|
1335
|
+
* Maps a ISO 4217 number to a ISO 4217 currency code and decimal positions
|
|
1336
|
+
*
|
|
1337
|
+
* @remarks
|
|
1338
|
+
* @see {@link CURRENCIES}
|
|
1339
|
+
*
|
|
1340
|
+
* @public
|
|
1341
|
+
*/
|
|
1342
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
941
1343
|
const REV_CURRENCIES = {};
|
|
942
1344
|
for (const [key, value] of Object.entries(CURRENCIES)) {
|
|
943
1345
|
REV_CURRENCIES[value.num] = {
|
|
@@ -946,7 +1348,72 @@ for (const [key, value] of Object.entries(CURRENCIES)) {
|
|
|
946
1348
|
};
|
|
947
1349
|
}
|
|
948
1350
|
|
|
949
|
-
|
|
1351
|
+
/**
|
|
1352
|
+
* Maps an ISO 639-1 language code to redsys language number
|
|
1353
|
+
*
|
|
1354
|
+
* @remarks
|
|
1355
|
+
* Some ISO 639-1 language codes may include more more than one redsys language number. In such cases the main redsys language number is used.
|
|
1356
|
+
*
|
|
1357
|
+
* @public
|
|
1358
|
+
*/
|
|
1359
|
+
// The Record key makes sure that all members of the key union type are defined.
|
|
1360
|
+
const LANGUAGES = {
|
|
1361
|
+
es: '1',
|
|
1362
|
+
en: '2',
|
|
1363
|
+
ca: '3',
|
|
1364
|
+
fr: '4',
|
|
1365
|
+
de: '5',
|
|
1366
|
+
nl: '6',
|
|
1367
|
+
it: '7',
|
|
1368
|
+
sv: '8',
|
|
1369
|
+
pt: '9',
|
|
1370
|
+
pl: '11',
|
|
1371
|
+
gl: '12',
|
|
1372
|
+
eu: '13',
|
|
1373
|
+
// Redsys ha utilizado ISO 3166-1(pais) como codigo de idioma para el resto
|
|
1374
|
+
bg: '100',
|
|
1375
|
+
zh: '156',
|
|
1376
|
+
hr: '191',
|
|
1377
|
+
cs: '203',
|
|
1378
|
+
da: '208',
|
|
1379
|
+
et: '233',
|
|
1380
|
+
fi: '246',
|
|
1381
|
+
el: '300',
|
|
1382
|
+
hu: '348',
|
|
1383
|
+
ja: '392',
|
|
1384
|
+
lv: '428',
|
|
1385
|
+
lt: '440',
|
|
1386
|
+
mt: '470',
|
|
1387
|
+
ro: '642',
|
|
1388
|
+
ru: '643',
|
|
1389
|
+
sk: '703',
|
|
1390
|
+
sl: '705',
|
|
1391
|
+
tr: '792'
|
|
1392
|
+
};
|
|
1393
|
+
/**
|
|
1394
|
+
* Maps a redsys language code to an ISO 639-1 language code
|
|
1395
|
+
*
|
|
1396
|
+
* @remarks
|
|
1397
|
+
* @see {@link LANGUAGES}
|
|
1398
|
+
*
|
|
1399
|
+
* @public
|
|
1400
|
+
*/
|
|
1401
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
1402
|
+
const REV_LANGUAGES = {};
|
|
1403
|
+
for (const [key, value] of Object.entries(LANGUAGES)) {
|
|
1404
|
+
REV_LANGUAGES[value] = key;
|
|
1405
|
+
}
|
|
1406
|
+
REV_LANGUAGES['10'] = 'ca';
|
|
1407
|
+
|
|
1408
|
+
/**
|
|
1409
|
+
* Maps ISO 3166 Alpha 2 codes to UN M49 numbers
|
|
1410
|
+
*
|
|
1411
|
+
* @remarks
|
|
1412
|
+
* [UN M49 to ISO 3166 information](https://unstats.un.org/unsd/methodology/m49/)
|
|
1413
|
+
*
|
|
1414
|
+
* @public
|
|
1415
|
+
*/
|
|
1416
|
+
// The Record key makes sure that all members of the key union type are defined.
|
|
950
1417
|
const COUNTRIES = {
|
|
951
1418
|
af: '4',
|
|
952
1419
|
al: '8',
|
|
@@ -1198,344 +1665,26 @@ const COUNTRIES = {
|
|
|
1198
1665
|
ye: '887',
|
|
1199
1666
|
zm: '894'
|
|
1200
1667
|
};
|
|
1668
|
+
/**
|
|
1669
|
+
* Maps UN M49 numbers to ISO 3166 Alpha 2 codes
|
|
1670
|
+
*
|
|
1671
|
+
* @remarks
|
|
1672
|
+
* @see {@link COUNTRIES}
|
|
1673
|
+
*
|
|
1674
|
+
* @public
|
|
1675
|
+
*/
|
|
1676
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
1201
1677
|
const REV_COUNTRIES = {};
|
|
1202
1678
|
for (const [key, value] of Object.entries(COUNTRIES)) {
|
|
1203
1679
|
REV_COUNTRIES[value] = key;
|
|
1204
1680
|
}
|
|
1205
1681
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
case 'expiryYear':
|
|
1213
|
-
case 'raw':
|
|
1214
|
-
continue;
|
|
1215
|
-
case 'order':
|
|
1216
|
-
obj.DS_MERCHANT_ORDER = input[key];
|
|
1217
|
-
break;
|
|
1218
|
-
case 'merchantCode':
|
|
1219
|
-
obj.DS_MERCHANT_MERCHANTCODE = input[key];
|
|
1220
|
-
break;
|
|
1221
|
-
case 'transactionType':
|
|
1222
|
-
obj.DS_MERCHANT_TRANSACTIONTYPE = input.transactionType;
|
|
1223
|
-
break;
|
|
1224
|
-
case 'terminal': {
|
|
1225
|
-
const value = input[key];
|
|
1226
|
-
if (!isStringNotEmpty(value))
|
|
1227
|
-
continue;
|
|
1228
|
-
obj.DS_MERCHANT_TERMINAL = value;
|
|
1229
|
-
break;
|
|
1230
|
-
}
|
|
1231
|
-
case 'currency': {
|
|
1232
|
-
const value = input.currency;
|
|
1233
|
-
if (!isStringNotEmpty(value))
|
|
1234
|
-
continue;
|
|
1235
|
-
const currencyData = CURRENCIES[value];
|
|
1236
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1237
|
-
if (!currencyData || !currencyData.num) {
|
|
1238
|
-
throw new ValidationError('Unsupported currency', value, 'currency');
|
|
1239
|
-
}
|
|
1240
|
-
obj.DS_MERCHANT_CURRENCY = currencyData.num.padStart(3, '0');
|
|
1241
|
-
break;
|
|
1242
|
-
}
|
|
1243
|
-
case 'amount': {
|
|
1244
|
-
/*
|
|
1245
|
-
* Experimental feature!!!
|
|
1246
|
-
*
|
|
1247
|
-
* The feature amountType='float' may be removed any time.
|
|
1248
|
-
* If you like this feature or have any comment please open an issue
|
|
1249
|
-
*
|
|
1250
|
-
* amountType can be atomic or float.
|
|
1251
|
-
* - atomic: amount represents the smallest currency unit. 1130 (EUR) -> 11.30 EUR
|
|
1252
|
-
* - float: amount is rounded to last decimal precision. 11.299999999 (EUR) -> 11.30 EUR
|
|
1253
|
-
*
|
|
1254
|
-
* Float amount can lead to losing precision, specially after doing
|
|
1255
|
-
* operations. Atomic is more precise for monetary amount, no lost cents,
|
|
1256
|
-
* better for accounting.
|
|
1257
|
-
*
|
|
1258
|
-
*/
|
|
1259
|
-
const value = input[key];
|
|
1260
|
-
// atomic by default
|
|
1261
|
-
let rawValue = value.toString();
|
|
1262
|
-
if ((options === null || options === void 0 ? void 0 : options.amountType) != null) {
|
|
1263
|
-
if (options.amountType === 'float') {
|
|
1264
|
-
const currencyData = CURRENCIES[input.currency];
|
|
1265
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1266
|
-
if (!currencyData || !currencyData.decimals) {
|
|
1267
|
-
throw new ValidationError('Unsupported currency', input.currency, 'currency');
|
|
1268
|
-
}
|
|
1269
|
-
const numericValue = typeof value === 'number' ? value : Number.parseFloat(value);
|
|
1270
|
-
if (!Number.isFinite(numericValue)) {
|
|
1271
|
-
throw new ValidationError('Invalid amount', value, 'amount');
|
|
1272
|
-
}
|
|
1273
|
-
rawValue = Math.round(numericValue * Math.pow(10, currencyData.decimals)).toString();
|
|
1274
|
-
}
|
|
1275
|
-
else if (options.amountType !== 'atomic') {
|
|
1276
|
-
throw new ValidationError('Unknown format', options.amountType, 'amountType');
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
if (rawValue.length > 12) {
|
|
1280
|
-
throw new ValidationError('Amount to charge is too large', value, 'amount');
|
|
1281
|
-
}
|
|
1282
|
-
obj.DS_MERCHANT_AMOUNT = rawValue;
|
|
1283
|
-
break;
|
|
1284
|
-
}
|
|
1285
|
-
case 'merchantName': {
|
|
1286
|
-
const value = input[key];
|
|
1287
|
-
if (!isStringNotEmpty(value))
|
|
1288
|
-
continue;
|
|
1289
|
-
obj.DS_MERCHANT_MERCHANTNAME = value;
|
|
1290
|
-
break;
|
|
1291
|
-
}
|
|
1292
|
-
case 'merchantURL': {
|
|
1293
|
-
const value = input[key];
|
|
1294
|
-
if (!isStringNotEmpty(value))
|
|
1295
|
-
continue;
|
|
1296
|
-
obj.DS_MERCHANT_MERCHANTURL = value;
|
|
1297
|
-
break;
|
|
1298
|
-
}
|
|
1299
|
-
case 'merchantSignature': {
|
|
1300
|
-
const value = input[key];
|
|
1301
|
-
if (!isStringNotEmpty(value))
|
|
1302
|
-
continue;
|
|
1303
|
-
obj.DS_MERCHANT_MERCHANTSIGNATURE = value;
|
|
1304
|
-
break;
|
|
1305
|
-
}
|
|
1306
|
-
case 'successURL': {
|
|
1307
|
-
const value = input[key];
|
|
1308
|
-
if (!isStringNotEmpty(value))
|
|
1309
|
-
continue;
|
|
1310
|
-
obj.DS_MERCHANT_URLOK = value;
|
|
1311
|
-
break;
|
|
1312
|
-
}
|
|
1313
|
-
case 'errorURL': {
|
|
1314
|
-
const value = input[key];
|
|
1315
|
-
if (!isStringNotEmpty(value))
|
|
1316
|
-
continue;
|
|
1317
|
-
obj.DS_MERCHANT_URLKO = value;
|
|
1318
|
-
break;
|
|
1319
|
-
}
|
|
1320
|
-
case 'dateFrequency': {
|
|
1321
|
-
const value = input[key];
|
|
1322
|
-
if (!isStringNotEmpty(value))
|
|
1323
|
-
continue;
|
|
1324
|
-
obj.DS_MERCHANT_DATEFRECUENCY = value;
|
|
1325
|
-
break;
|
|
1326
|
-
}
|
|
1327
|
-
case 'chargeExpiryDate': {
|
|
1328
|
-
const value = input[key];
|
|
1329
|
-
if (!isStringNotEmpty(value))
|
|
1330
|
-
continue;
|
|
1331
|
-
obj.DS_MERCHANT_CHARGEEXPIRYDATE = value;
|
|
1332
|
-
break;
|
|
1333
|
-
}
|
|
1334
|
-
case 'sumTotal': {
|
|
1335
|
-
const value = input[key];
|
|
1336
|
-
if (!isStringNotEmpty(value))
|
|
1337
|
-
continue;
|
|
1338
|
-
obj.DS_MERCHANT_SUMTOTAL = value;
|
|
1339
|
-
break;
|
|
1340
|
-
}
|
|
1341
|
-
case 'directPayment': {
|
|
1342
|
-
const value = input[key];
|
|
1343
|
-
if (!isStringNotEmpty(value))
|
|
1344
|
-
continue;
|
|
1345
|
-
obj.DS_MERCHANT_DIRECTPAYMENT = value;
|
|
1346
|
-
break;
|
|
1347
|
-
}
|
|
1348
|
-
case 'identifier': {
|
|
1349
|
-
const value = input[key];
|
|
1350
|
-
if (!isStringNotEmpty(value))
|
|
1351
|
-
continue;
|
|
1352
|
-
obj.DS_MERCHANT_IDENTIFIER = value;
|
|
1353
|
-
break;
|
|
1354
|
-
}
|
|
1355
|
-
case 'group': {
|
|
1356
|
-
const value = input[key];
|
|
1357
|
-
if (!isStringNotEmpty(value))
|
|
1358
|
-
continue;
|
|
1359
|
-
obj.DS_MERCHANT_GROUP = value;
|
|
1360
|
-
break;
|
|
1361
|
-
}
|
|
1362
|
-
case 'pan': {
|
|
1363
|
-
const value = input[key];
|
|
1364
|
-
if (!isStringNotEmpty(value))
|
|
1365
|
-
continue;
|
|
1366
|
-
obj.DS_MERCHANT_PAN = value;
|
|
1367
|
-
break;
|
|
1368
|
-
}
|
|
1369
|
-
case 'expiryDate': {
|
|
1370
|
-
const stdFmt = input[key];
|
|
1371
|
-
if (!isStringNotEmpty(stdFmt))
|
|
1372
|
-
continue;
|
|
1373
|
-
if (stdFmt.length !== 4 || !/^\d+$/.test(stdFmt)) {
|
|
1374
|
-
throw new ValidationError('Invalid expiryDate', stdFmt, 'expiryDate');
|
|
1375
|
-
}
|
|
1376
|
-
const altFmt = `${stdFmt.slice(2, 4)}${stdFmt.slice(0, 2)}`;
|
|
1377
|
-
obj.DS_MERCHANT_EXPIRYDATE = altFmt;
|
|
1378
|
-
break;
|
|
1379
|
-
}
|
|
1380
|
-
case 'cvv': {
|
|
1381
|
-
const value = input[key];
|
|
1382
|
-
if (!isStringNotEmpty(value))
|
|
1383
|
-
continue;
|
|
1384
|
-
obj.DS_MERCHANT_CVV2 = value;
|
|
1385
|
-
break;
|
|
1386
|
-
}
|
|
1387
|
-
case 'cardCountry': {
|
|
1388
|
-
const value = input[key];
|
|
1389
|
-
if (!isStringNotEmpty(value))
|
|
1390
|
-
continue;
|
|
1391
|
-
const countryInt = COUNTRIES[value];
|
|
1392
|
-
if (countryInt != null && countryInt) {
|
|
1393
|
-
obj.DS_CARD_COUNTRY = countryInt;
|
|
1394
|
-
}
|
|
1395
|
-
break;
|
|
1396
|
-
}
|
|
1397
|
-
case 'lang': {
|
|
1398
|
-
const value = input[key];
|
|
1399
|
-
if (!isStringNotEmpty(value))
|
|
1400
|
-
continue;
|
|
1401
|
-
const langInt = LANGUAGES[value];
|
|
1402
|
-
if (langInt != null && langInt) {
|
|
1403
|
-
obj.DS_MERCHANT_CONSUMERLANGUAGE = langInt;
|
|
1404
|
-
}
|
|
1405
|
-
break;
|
|
1406
|
-
}
|
|
1407
|
-
case 'merchantData': {
|
|
1408
|
-
const value = input[key];
|
|
1409
|
-
if (!isStringNotEmpty(value))
|
|
1410
|
-
continue;
|
|
1411
|
-
obj.DS_MERCHANT_MERCHANTDATA = value;
|
|
1412
|
-
break;
|
|
1413
|
-
}
|
|
1414
|
-
case 'clientIp': {
|
|
1415
|
-
const value = input[key];
|
|
1416
|
-
if (!isStringNotEmpty(value))
|
|
1417
|
-
continue;
|
|
1418
|
-
obj.DS_MERCHANT_CLIENTIP = value;
|
|
1419
|
-
break;
|
|
1420
|
-
}
|
|
1421
|
-
case 'operationId': {
|
|
1422
|
-
const value = input[key];
|
|
1423
|
-
if (!isStringNotEmpty(value))
|
|
1424
|
-
continue;
|
|
1425
|
-
obj.DS_MERCHANT_IDOPER = value;
|
|
1426
|
-
break;
|
|
1427
|
-
}
|
|
1428
|
-
case 'payMethods': {
|
|
1429
|
-
const value = input[key];
|
|
1430
|
-
if (!isStringNotEmpty(value))
|
|
1431
|
-
continue;
|
|
1432
|
-
obj.DS_MERCHANT_PAYMETHODS = value;
|
|
1433
|
-
break;
|
|
1434
|
-
}
|
|
1435
|
-
case 'productDescription': {
|
|
1436
|
-
const value = input[key];
|
|
1437
|
-
if (!isStringNotEmpty(value))
|
|
1438
|
-
continue;
|
|
1439
|
-
obj.DS_MERCHANT_PRODUCTDESCRIPTION = value;
|
|
1440
|
-
break;
|
|
1441
|
-
}
|
|
1442
|
-
case 'taxReference': {
|
|
1443
|
-
const value = input[key];
|
|
1444
|
-
if (!isStringNotEmpty(value))
|
|
1445
|
-
continue;
|
|
1446
|
-
obj.DS_MERCHANT_TAX_REFERENCE = value;
|
|
1447
|
-
break;
|
|
1448
|
-
}
|
|
1449
|
-
case 'transactionDate': {
|
|
1450
|
-
const value = input[key];
|
|
1451
|
-
if (!isStringNotEmpty(value))
|
|
1452
|
-
continue;
|
|
1453
|
-
obj.DS_MERCHANT_TRANSACTIONDATE = value;
|
|
1454
|
-
break;
|
|
1455
|
-
}
|
|
1456
|
-
case 'merchantDescriptor': {
|
|
1457
|
-
const value = input[key];
|
|
1458
|
-
if (!isStringNotEmpty(value))
|
|
1459
|
-
continue;
|
|
1460
|
-
obj.DS_MERCHANT_MERCHANTDESCRIPTOR = value;
|
|
1461
|
-
break;
|
|
1462
|
-
}
|
|
1463
|
-
case 'customerMobile': {
|
|
1464
|
-
const value = input[key];
|
|
1465
|
-
if (!isStringNotEmpty(value))
|
|
1466
|
-
continue;
|
|
1467
|
-
obj.DS_MERCHANT_CUSTOMER_MOBILE = value;
|
|
1468
|
-
break;
|
|
1469
|
-
}
|
|
1470
|
-
case 'customerMail': {
|
|
1471
|
-
const value = input[key];
|
|
1472
|
-
if (!isStringNotEmpty(value))
|
|
1473
|
-
continue;
|
|
1474
|
-
obj.DS_MERCHANT_CUSTOMER_MAIL = value;
|
|
1475
|
-
break;
|
|
1476
|
-
}
|
|
1477
|
-
case 'cardHolder': {
|
|
1478
|
-
const value = input[key];
|
|
1479
|
-
if (!isStringNotEmpty(value))
|
|
1480
|
-
continue;
|
|
1481
|
-
obj.DS_MERCHANT_TITULAR = value;
|
|
1482
|
-
break;
|
|
1483
|
-
}
|
|
1484
|
-
case 'smsTemplate': {
|
|
1485
|
-
const value = input[key];
|
|
1486
|
-
if (!isStringNotEmpty(value))
|
|
1487
|
-
continue;
|
|
1488
|
-
obj.DS_MERCHANT_CUSTOMER_SMS_TEXT = value;
|
|
1489
|
-
break;
|
|
1490
|
-
}
|
|
1491
|
-
default:
|
|
1492
|
-
throw new ValidationError('Unknown parameter', undefined, key);
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
return obj;
|
|
1496
|
-
};
|
|
1497
|
-
const formatParams = (paramsInput, options) => {
|
|
1498
|
-
// Pre processing
|
|
1499
|
-
const { amount, currency, merchantCode, transactionType, order, expiryYear, expiryMonth, expiryDate } = paramsInput;
|
|
1500
|
-
if ((typeof amount !== 'string' || !amount) &&
|
|
1501
|
-
(typeof amount !== 'number' || amount < 0)) {
|
|
1502
|
-
// An amount of 0 is valid, it may be used for obtaining a credit card reference(tokenization)
|
|
1503
|
-
throw new ValidationError('Invalid amount', amount, 'amount');
|
|
1504
|
-
}
|
|
1505
|
-
if (!currency) {
|
|
1506
|
-
throw new ValidationError('No currency provided', currency, 'currency');
|
|
1507
|
-
}
|
|
1508
|
-
if (!merchantCode) {
|
|
1509
|
-
throw new ValidationError('The merchant code is mandatory', merchantCode, 'merchantCode');
|
|
1510
|
-
}
|
|
1511
|
-
if (!transactionType) {
|
|
1512
|
-
throw new ValidationError('The transaction type is mandatory', transactionType, 'transactionType');
|
|
1513
|
-
}
|
|
1514
|
-
if (!order) {
|
|
1515
|
-
throw new ValidationError('No order reference provided', order, 'order');
|
|
1516
|
-
}
|
|
1517
|
-
if ((expiryDate == null || !expiryDate) &&
|
|
1518
|
-
(expiryMonth != null && expiryMonth) &&
|
|
1519
|
-
(expiryYear != null && expiryYear)) {
|
|
1520
|
-
if (expiryMonth.length !== 2) {
|
|
1521
|
-
throw new ValidationError('Invalid expiryMonth', expiryMonth, 'expiryMonth');
|
|
1522
|
-
}
|
|
1523
|
-
if (expiryYear.length !== 2) {
|
|
1524
|
-
throw new ValidationError('Invalid expiryYear', expiryYear, 'expiryYear');
|
|
1525
|
-
}
|
|
1526
|
-
paramsInput.expiryDate = `${expiryMonth}${expiryYear}`;
|
|
1527
|
-
}
|
|
1528
|
-
// Defaults
|
|
1529
|
-
if (paramsInput.terminal == null || !paramsInput.terminal)
|
|
1530
|
-
paramsInput.terminal = '1';
|
|
1531
|
-
const paramsObj = inputParamsFormatter(paramsInput, options);
|
|
1532
|
-
return {
|
|
1533
|
-
...paramsObj,
|
|
1534
|
-
// Last, so raw doesn't get overwritten
|
|
1535
|
-
...paramsInput.raw
|
|
1536
|
-
};
|
|
1537
|
-
};
|
|
1538
|
-
|
|
1682
|
+
/**
|
|
1683
|
+
* Card brand name to number
|
|
1684
|
+
*
|
|
1685
|
+
* @public
|
|
1686
|
+
*/
|
|
1687
|
+
// The Record key makes sure that all members of the key union type are defined.
|
|
1539
1688
|
const CARDBRANDS = {
|
|
1540
1689
|
VISA: '1',
|
|
1541
1690
|
MASTERCARD: '2',
|
|
@@ -1545,310 +1694,404 @@ const CARDBRANDS = {
|
|
|
1545
1694
|
JCB: '9',
|
|
1546
1695
|
UPI: '22'
|
|
1547
1696
|
};
|
|
1697
|
+
/**
|
|
1698
|
+
* Card brand number to name
|
|
1699
|
+
*
|
|
1700
|
+
* @remarks
|
|
1701
|
+
* @see {@link CARDBRANDS}
|
|
1702
|
+
*
|
|
1703
|
+
* @public
|
|
1704
|
+
*/
|
|
1705
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
1548
1706
|
const REV_CARDBRANDS = {};
|
|
1549
1707
|
for (const [key, value] of Object.entries(CARDBRANDS)) {
|
|
1550
1708
|
REV_CARDBRANDS[value] = key;
|
|
1551
1709
|
}
|
|
1552
1710
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
obj.merchantData = value;
|
|
1602
|
-
},
|
|
1603
|
-
DS_SECUREPAYMENT: (obj, value) => {
|
|
1604
|
-
obj.securePayment = value === '1';
|
|
1605
|
-
},
|
|
1606
|
-
DS_TRANSACTIONTYPE: (obj, value) => {
|
|
1607
|
-
obj.transactionType = value;
|
|
1608
|
-
},
|
|
1609
|
-
// Pago por referencia
|
|
1610
|
-
DS_MERCHANT_IDENTIFIER: (obj, value) => {
|
|
1611
|
-
obj.identifier = value;
|
|
1612
|
-
},
|
|
1613
|
-
DS_MERCHANT_GROUP: (obj, value) => {
|
|
1614
|
-
obj.merchantGroup = value;
|
|
1615
|
-
},
|
|
1616
|
-
DS_EXPIRYDATE: (obj, value) => {
|
|
1617
|
-
obj.expiryMonth = value.slice(2, 4);
|
|
1618
|
-
obj.expiryYear = value.slice(0, 2);
|
|
1619
|
-
obj.expiryDate = `${obj.expiryMonth}${obj.expiryYear}`;
|
|
1620
|
-
},
|
|
1621
|
-
// Optional
|
|
1622
|
-
DS_CARDNUMBER: (obj, value) => {
|
|
1623
|
-
obj.cardNumber = value;
|
|
1624
|
-
},
|
|
1625
|
-
DS_CARD_TYPE: (obj, value) => {
|
|
1626
|
-
obj.cardType = value;
|
|
1627
|
-
},
|
|
1628
|
-
DS_AUTHORISATIONCODE: (obj, value) => {
|
|
1629
|
-
obj.authorisationCode = value;
|
|
1630
|
-
},
|
|
1631
|
-
DS_CONSUMERLANGUAGE: langFormatter,
|
|
1632
|
-
DS_LANGUAGE: langFormatter,
|
|
1633
|
-
DS_CARD_COUNTRY: (obj, value) => {
|
|
1634
|
-
const cardCountryInt = Number.parseInt(value);
|
|
1635
|
-
const cardCountry = REV_COUNTRIES[cardCountryInt];
|
|
1636
|
-
if (cardCountry != null && cardCountry) {
|
|
1637
|
-
obj.cardCountry = cardCountry;
|
|
1638
|
-
}
|
|
1639
|
-
},
|
|
1640
|
-
DS_CARD_BRAND: (obj, value) => {
|
|
1641
|
-
const cardBrandInt = Number.parseInt(value);
|
|
1642
|
-
const cardBrand = REV_CARDBRANDS[cardBrandInt];
|
|
1643
|
-
if (cardBrand != null && cardBrand) {
|
|
1644
|
-
obj.cardBrand = cardBrand;
|
|
1645
|
-
}
|
|
1646
|
-
},
|
|
1647
|
-
DS_URLPAGO2FASES: (obj, value) => {
|
|
1648
|
-
obj.payURL = value;
|
|
1649
|
-
}
|
|
1650
|
-
};
|
|
1651
|
-
const formatResponse = (rawParams) => {
|
|
1652
|
-
const obj = { raw: rawParams };
|
|
1653
|
-
for (const [key, value] of Object.entries(rawParams)) {
|
|
1654
|
-
const formatter = responseFormatters[key.toUpperCase()];
|
|
1655
|
-
if (!formatter)
|
|
1656
|
-
continue;
|
|
1657
|
-
formatter(obj, value);
|
|
1658
|
-
}
|
|
1659
|
-
// Post formatting
|
|
1660
|
-
if (obj.date != null && obj.date && obj.hour != null && obj.hour) {
|
|
1661
|
-
obj.timestamp = dayjs__default['default'].tz(`${obj.date} ${obj.hour}`, 'YYYY-MM-DD HH:mm', 'Europe/Madrid').toDate();
|
|
1662
|
-
}
|
|
1663
|
-
return obj;
|
|
1711
|
+
/**
|
|
1712
|
+
* Transaction type enum
|
|
1713
|
+
*
|
|
1714
|
+
* @public
|
|
1715
|
+
*/
|
|
1716
|
+
const TRANSACTION_TYPES = {
|
|
1717
|
+
/** Autorización */
|
|
1718
|
+
AUTHORIZATION: '0',
|
|
1719
|
+
/** Preautorización */
|
|
1720
|
+
PRE_AUTHORIZATION: '1',
|
|
1721
|
+
/** Confirmación de preautorización */
|
|
1722
|
+
CONFIRMATION: '2',
|
|
1723
|
+
/** Devolución Automática */
|
|
1724
|
+
AUTO_REFUND: '3',
|
|
1725
|
+
/** Transacción Recurrente */
|
|
1726
|
+
RECURRING_TRANSACTION: '5',
|
|
1727
|
+
/** Transacción Sucesiva */
|
|
1728
|
+
SUCCESSIVE_TRANSACTION: '6',
|
|
1729
|
+
/** Pre-autenticación */
|
|
1730
|
+
PRE_AUTHENTICATION: '7',
|
|
1731
|
+
/** Confirmación de pre-autenticación */
|
|
1732
|
+
PRE_AUTHENTICATION_CONFIRMATION: '8',
|
|
1733
|
+
/** Anulación de Preautorización */
|
|
1734
|
+
PRE_AUTHORIZATION_CANCEL: '9',
|
|
1735
|
+
/** Autorización en diferido */
|
|
1736
|
+
DEFERRED_AUTHORIZATION: 'O',
|
|
1737
|
+
/** Paygold */
|
|
1738
|
+
PAYGOLD: '15',
|
|
1739
|
+
/** Autenticación Puce */
|
|
1740
|
+
PUCE_AUTH: '17',
|
|
1741
|
+
/** Devolución sin original */
|
|
1742
|
+
RETURN_WITHOUT_ORIGINAL: '34',
|
|
1743
|
+
/** Premio de apuestas */
|
|
1744
|
+
BETTING_PAYOUT: '37',
|
|
1745
|
+
/** Eliminar referencia */
|
|
1746
|
+
DELETE_REFERENCE: '44',
|
|
1747
|
+
/** Operaciones sin autenticacíon */
|
|
1748
|
+
NO_AUTHENTICATION: 'A',
|
|
1749
|
+
/** Paygold */
|
|
1750
|
+
PAYLINK: 'F',
|
|
1751
|
+
/** Confirmación de autorización en diferido */
|
|
1752
|
+
DEFERRED_AUTHORIZATION_CONFIRMATION: 'P',
|
|
1753
|
+
/** Anulación de autorización en diferido */
|
|
1754
|
+
DEFERRED_AUTHORIZATION_CANCEL: 'Q',
|
|
1755
|
+
/** Cuota inicial diferido */
|
|
1756
|
+
DEFERRED_INITIAL_FEE: 'R',
|
|
1757
|
+
/** Cuota sucesiva diferido */
|
|
1758
|
+
DEFERRED_SUCCESSIVE_FEE: 'S'
|
|
1664
1759
|
};
|
|
1665
1760
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1761
|
+
/**
|
|
1762
|
+
* Applies an input formatter to the first argument of a function
|
|
1763
|
+
*
|
|
1764
|
+
* @remarks
|
|
1765
|
+
* The signature of this function is so complex because tries to suport
|
|
1766
|
+
* formatters with generics that provide a default type. This should remain
|
|
1767
|
+
* until [variadic generics](https://github.com/microsoft/TypeScript/issues/5453)
|
|
1768
|
+
* land in typescript
|
|
1769
|
+
*
|
|
1770
|
+
* @public
|
|
1771
|
+
*/
|
|
1772
|
+
const useSingleInputFormatter = (fn, inputFormatter) => {
|
|
1773
|
+
return (input, ...otherArgs) => fn(inputFormatter(input), ...otherArgs);
|
|
1670
1774
|
};
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1775
|
+
/**
|
|
1776
|
+
* Applies an output formatter to a function
|
|
1777
|
+
*
|
|
1778
|
+
* @public
|
|
1779
|
+
*/
|
|
1780
|
+
const useOutputFormatter = (fn, outputFormatter) => {
|
|
1781
|
+
return (a) => outputFormatter(fn(a));
|
|
1674
1782
|
};
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
}
|
|
1690
|
-
redirectPetitionParameters(paramsInput, options) {
|
|
1691
|
-
const paramsObj = formatParams(paramsInput, options);
|
|
1692
|
-
// Docs escape "/" but we don't, this JSON won't get placed in a script tag
|
|
1693
|
-
return Buffer.from(JSON.stringify(paramsObj), 'utf8').toString('base64');
|
|
1694
|
-
}
|
|
1695
|
-
redirectPetition(paramsInput, options) {
|
|
1696
|
-
const paramsString = this.redirectPetitionParameters(paramsInput, options);
|
|
1697
|
-
const signature = sha256Sign(this.secretKey, paramsInput.order, paramsString);
|
|
1698
|
-
return {
|
|
1699
|
-
url: this.urls.redirect,
|
|
1700
|
-
body: {
|
|
1701
|
-
Ds_SignatureVersion: 'HMAC_SHA256_V1',
|
|
1702
|
-
Ds_MerchantParameters: paramsString,
|
|
1703
|
-
Ds_Signature: signature
|
|
1704
|
-
}
|
|
1705
|
-
};
|
|
1706
|
-
}
|
|
1707
|
-
processNotificationParameters(strPayload) {
|
|
1708
|
-
if (!strPayload) {
|
|
1709
|
-
throw new ParseError('Payload is required');
|
|
1710
|
-
}
|
|
1711
|
-
if (typeof strPayload !== 'string') {
|
|
1712
|
-
throw new ParseError('Payload must be a base-64 encoded string');
|
|
1713
|
-
}
|
|
1714
|
-
const payload = JSON.parse(decodeURIComponent(Buffer.from(strPayload, 'base64').toString('utf8')));
|
|
1715
|
-
if (typeof payload !== 'object' || payload == null) {
|
|
1716
|
-
throw new ParseError('Cannot parse notification payload');
|
|
1717
|
-
}
|
|
1718
|
-
return payload;
|
|
1719
|
-
}
|
|
1720
|
-
processNotification(body) {
|
|
1721
|
-
const params = this.processNotificationParameters(body.Ds_MerchantParameters);
|
|
1722
|
-
const order = params.Ds_Order;
|
|
1723
|
-
if (order == null || !order) {
|
|
1724
|
-
throw new ParseError('Invalid notification parameters');
|
|
1725
|
-
}
|
|
1726
|
-
const signature = body.Ds_Signature;
|
|
1727
|
-
// Base64url with padding, only substitutes + and /
|
|
1728
|
-
const expSignature = sha256Sign(this.secretKey, order, body.Ds_MerchantParameters);
|
|
1729
|
-
// Comparing different base64 encodings is messy. Foolproof, compare buffers.
|
|
1730
|
-
if (!signature || !Buffer.from(expSignature, 'base64').equals(base64url__default['default'].toBuffer(signature))) {
|
|
1731
|
-
throw new ParseError('Invalid signature');
|
|
1732
|
-
}
|
|
1733
|
-
return formatResponse(params);
|
|
1734
|
-
}
|
|
1735
|
-
processXMLResponseData(xmlData) {
|
|
1736
|
-
const parsedResponse = xmlParser__default['default'].parse(xmlData, { parseNodeValue: false });
|
|
1737
|
-
if (typeof parsedResponse !== 'object' || parsedResponse == null) {
|
|
1738
|
-
throw new ParseError('Cannot parse notification payload');
|
|
1739
|
-
}
|
|
1740
|
-
return parsedResponse.RETORNOXML;
|
|
1741
|
-
}
|
|
1742
|
-
// Generic Processor for Web Service Responses
|
|
1743
|
-
processXMLResponse(xmlData) {
|
|
1744
|
-
const data = this.processXMLResponseData(xmlData);
|
|
1745
|
-
if (data.CODIGO !== '0') {
|
|
1746
|
-
const err = new ParseError(`Redsys error ${data.CODIGO}`);
|
|
1747
|
-
err.code = data.CODIGO;
|
|
1748
|
-
err.description = getSISErrorCodeMessage(data.CODIGO);
|
|
1749
|
-
// Can't access data.OPERACION, only data.RECIBIDO
|
|
1750
|
-
// However data.RECIBIDO may contain sensitive information
|
|
1751
|
-
throw err;
|
|
1752
|
-
}
|
|
1753
|
-
return data;
|
|
1783
|
+
/**
|
|
1784
|
+
* Applies an output formatter to the resolved ouput promise of a function
|
|
1785
|
+
*
|
|
1786
|
+
* @public
|
|
1787
|
+
*/
|
|
1788
|
+
const usePromiseOutputFormatter = (fn, outputFormatter) => {
|
|
1789
|
+
return async (a) => outputFormatter(await fn(a));
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
const formatInputCurrency = (input) => {
|
|
1793
|
+
const currencyData = CURRENCIES[input];
|
|
1794
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1795
|
+
if (!currencyData || !currencyData.num) {
|
|
1796
|
+
throw new ValidationError('Unsupported currency', { currency: input });
|
|
1754
1797
|
}
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
const value = operationData[field] || operationData[field.toUpperCase()];
|
|
1761
|
-
if (value != null && value) {
|
|
1762
|
-
signedValues.push(value);
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
const signedString = signedValues.join('');
|
|
1766
|
-
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing
|
|
1767
|
-
const orderId = operationData.Ds_Order || operationData.DS_ORDER;
|
|
1768
|
-
if (orderId == null || !orderId) {
|
|
1769
|
-
throw new ParseError('Invalid signature');
|
|
1770
|
-
}
|
|
1771
|
-
const signature = operationData.Ds_Signature;
|
|
1772
|
-
const expSignature = sha256Sign(this.secretKey, orderId, signedString);
|
|
1773
|
-
if (signature == null || !signature || signature !== expSignature) {
|
|
1774
|
-
throw new ParseError('Invalid signature');
|
|
1775
|
-
}
|
|
1798
|
+
return currencyData.num;
|
|
1799
|
+
};
|
|
1800
|
+
const formatExpiryDate = ({ expiryYear, expiryMonth }) => {
|
|
1801
|
+
if ((expiryMonth === null || expiryMonth === void 0 ? void 0 : expiryMonth.length) !== 2) {
|
|
1802
|
+
throw new ValidationError('Invalid expiryMonth', { expiryMonth: expiryMonth });
|
|
1776
1803
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
const data = this.processXMLResponse(xml);
|
|
1780
|
-
this.assertXMLPetitionResponseSignature(data);
|
|
1781
|
-
return formatResponse(data.OPERACION);
|
|
1804
|
+
if ((expiryYear === null || expiryYear === void 0 ? void 0 : expiryYear.length) !== 2) {
|
|
1805
|
+
throw new ValidationError('Invalid expiryYear', { expiryYear: expiryYear });
|
|
1782
1806
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1807
|
+
return `${expiryYear}${expiryMonth}`;
|
|
1808
|
+
};
|
|
1809
|
+
/**
|
|
1810
|
+
* Converts a monetary amount from a decimal string currency unit to the atomic currency unit
|
|
1811
|
+
*/
|
|
1812
|
+
const formatAmount = (value, context) => {
|
|
1813
|
+
const { currency } = context;
|
|
1814
|
+
if (!currency) {
|
|
1815
|
+
throw new ValidationError('Missing currency', { currency: currency });
|
|
1786
1816
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1817
|
+
const currencyData = CURRENCIES[currency];
|
|
1818
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1819
|
+
if (!currencyData || !currencyData.decimals) {
|
|
1820
|
+
throw new ValidationError('Unsupported currency', { currency: currency });
|
|
1791
1821
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
}
|
|
1797
|
-
// Promise
|
|
1798
|
-
return await this.wsClient;
|
|
1822
|
+
const rawValue = typeof value === 'number'
|
|
1823
|
+
? Math.round(value * Math.pow(10, currencyData.decimals)).toString()
|
|
1824
|
+
: new Decimal__default['default'](value).mul(Math.pow(10, currencyData.decimals)).round().toFixed(0);
|
|
1825
|
+
if (rawValue.length > 12) {
|
|
1826
|
+
throw new ValidationError('Amount to charge is too large', { amount: value });
|
|
1799
1827
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
}
|
|
1806
|
-
const res = await client.trataPeticionAsync({ datoEntrada: peticion });
|
|
1807
|
-
const result = res[0].trataPeticionReturn;
|
|
1808
|
-
const parsedResponse = this.processXMLPetitionResponse(result);
|
|
1809
|
-
const resCode = parsedResponse.response;
|
|
1810
|
-
if (resCode != null && resCode > 100 && resCode !== 400 && resCode !== 600) {
|
|
1811
|
-
throw new GatewayError('Error in webservice petition', resCode, res);
|
|
1812
|
-
}
|
|
1813
|
-
return parsedResponse;
|
|
1828
|
+
return rawValue;
|
|
1829
|
+
};
|
|
1830
|
+
const formatLang$1 = (value) => {
|
|
1831
|
+
const langInt = LANGUAGES[value];
|
|
1832
|
+
if (langInt == null) {
|
|
1833
|
+
throw new ValidationError('Invalid lang code', { lang: value });
|
|
1814
1834
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
const signature = msg.Signature;
|
|
1826
|
-
const order = msg.Request.Ds_Order;
|
|
1827
|
-
if (order == null || !order) {
|
|
1828
|
-
throw new ParseError('Invalid notification response');
|
|
1829
|
-
}
|
|
1830
|
-
const expSignature = sha256Sign(this.secretKey, order, signedStr);
|
|
1831
|
-
if (!signature || signature !== expSignature) {
|
|
1832
|
-
throw new ParseError('Invalid signature', signature, xml);
|
|
1833
|
-
}
|
|
1834
|
-
return formatResponse(msg.Request);
|
|
1835
|
+
return langInt;
|
|
1836
|
+
};
|
|
1837
|
+
const baseInputFormatter = (input) => {
|
|
1838
|
+
const { order, merchantCode, transactionType, terminal, amount, currency, merchantName, identifier, group, expiryYear, expiryMonth, pan, cvv, directPayment, merchantData, operationId, productDescription, taxReference, transactionDate, cardHolder, raw, ...unknownInput } = input;
|
|
1839
|
+
const unknownKeys = Object.keys(unknownInput);
|
|
1840
|
+
if (unknownKeys.length > 0) {
|
|
1841
|
+
throw new ValidationError([
|
|
1842
|
+
`Unknown parameters: ${unknownKeys.join(', ')}`,
|
|
1843
|
+
'To pass raw parameters, use "raw"'
|
|
1844
|
+
].join('\n'), unknownInput);
|
|
1835
1845
|
}
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1846
|
+
return {
|
|
1847
|
+
DS_MERCHANT_ORDER: order,
|
|
1848
|
+
DS_MERCHANT_MERCHANTCODE: merchantCode,
|
|
1849
|
+
DS_MERCHANT_TRANSACTIONTYPE: transactionType,
|
|
1850
|
+
DS_MERCHANT_TERMINAL: terminal,
|
|
1851
|
+
...(amount != null ? { DS_MERCHANT_AMOUNT: formatAmount(amount, { currency }) } : undefined),
|
|
1852
|
+
...(isStringNotEmpty(currency) ? { DS_MERCHANT_CURRENCY: formatInputCurrency(currency) } : undefined),
|
|
1853
|
+
...(isStringNotEmpty(merchantName) ? { DS_MERCHANT_MERCHANTNAME: merchantName } : undefined),
|
|
1854
|
+
...(isStringNotEmpty(identifier) ? { DS_MERCHANT_IDENTIFIER: identifier } : undefined),
|
|
1855
|
+
...(isStringNotEmpty(group) ? { DS_MERCHANT_GROUP: group } : undefined),
|
|
1856
|
+
...(isStringNotEmpty(expiryYear) || isStringNotEmpty(expiryMonth) ? { DS_MERCHANT_EXPIRYDATE: formatExpiryDate({ expiryYear, expiryMonth }) } : undefined),
|
|
1857
|
+
...(isStringNotEmpty(pan) ? { DS_MERCHANT_PAN: pan } : undefined),
|
|
1858
|
+
...(isStringNotEmpty(cvv) ? { DS_MERCHANT_CVV2: cvv } : undefined),
|
|
1859
|
+
...(isStringNotEmpty(directPayment) ? { DS_MERCHANT_DIRECTPAYMENT: directPayment } : undefined),
|
|
1860
|
+
...(isStringNotEmpty(merchantData) ? { DS_MERCHANT_MERCHANTDATA: merchantData } : undefined),
|
|
1861
|
+
...(isStringNotEmpty(operationId) ? { DS_MERCHANT_IDOPER: operationId } : undefined),
|
|
1862
|
+
...(isStringNotEmpty(productDescription) ? { DS_MERCHANT_PRODUCTDESCRIPTION: productDescription } : undefined),
|
|
1863
|
+
...(isStringNotEmpty(taxReference) ? { DS_MERCHANT_TAX_REFERENCE: taxReference } : undefined),
|
|
1864
|
+
...(isStringNotEmpty(transactionDate) ? { DS_MERCHANT_TRANSACTIONDATE: transactionDate } : undefined),
|
|
1865
|
+
...(isStringNotEmpty(cardHolder) ? { DS_MERCHANT_TITULAR: cardHolder } : undefined),
|
|
1866
|
+
...raw
|
|
1867
|
+
};
|
|
1868
|
+
};
|
|
1869
|
+
/**
|
|
1870
|
+
* Redirection input formatter
|
|
1871
|
+
*
|
|
1872
|
+
* @public
|
|
1873
|
+
*/
|
|
1874
|
+
const redirectInputFormatter = (input) => {
|
|
1875
|
+
const { merchantURL, successURL, errorURL, payMethods, lang, ...baseInput } = input;
|
|
1876
|
+
return {
|
|
1877
|
+
...baseInputFormatter(baseInput),
|
|
1878
|
+
...(isStringNotEmpty(merchantURL) ? { DS_MERCHANT_MERCHANTURL: merchantURL } : undefined),
|
|
1879
|
+
...(isStringNotEmpty(successURL) ? { DS_MERCHANT_URLOK: successURL } : undefined),
|
|
1880
|
+
...(isStringNotEmpty(errorURL) ? { DS_MERCHANT_URLKO: errorURL } : undefined),
|
|
1881
|
+
...(isStringNotEmpty(payMethods) ? { DS_MERCHANT_PAYMETHODS: payMethods } : undefined),
|
|
1882
|
+
...(isStringNotEmpty(lang) ? { DS_MERCHANT_CONSUMERLANGUAGE: formatLang$1(lang) } : undefined),
|
|
1883
|
+
// Overwrite formatted parameters
|
|
1884
|
+
...baseInput.raw
|
|
1885
|
+
};
|
|
1886
|
+
};
|
|
1887
|
+
const requestInputFormatter = (input) => {
|
|
1888
|
+
const { customerMail, customerMobile, smsTemplate, ...baseInput } = input;
|
|
1889
|
+
return {
|
|
1890
|
+
...baseInputFormatter(baseInput),
|
|
1891
|
+
...(isStringNotEmpty(customerMobile) ? { DS_MERCHANT_CUSTOMER_MOBILE: customerMobile } : undefined),
|
|
1892
|
+
...(isStringNotEmpty(customerMail) ? { DS_MERCHANT_CUSTOMER_MAIL: customerMail } : undefined),
|
|
1893
|
+
...(isStringNotEmpty(smsTemplate) ? { DS_MERCHANT_CUSTOMER_SMS_TEXT: smsTemplate } : undefined),
|
|
1894
|
+
// Overwrite formatted parameters
|
|
1895
|
+
...baseInput.raw
|
|
1896
|
+
};
|
|
1897
|
+
};
|
|
1898
|
+
/**
|
|
1899
|
+
* REST iniciaPeticion input formatter
|
|
1900
|
+
*
|
|
1901
|
+
* @public
|
|
1902
|
+
*/
|
|
1903
|
+
const restIniciaPeticionInputFormatter = (raw) => {
|
|
1904
|
+
const { emv3ds, ...requestInput } = raw;
|
|
1905
|
+
return {
|
|
1906
|
+
...requestInputFormatter(requestInput),
|
|
1907
|
+
...(emv3ds != null ? { DS_MERCHANT_EMV3DS: emv3ds } : undefined),
|
|
1908
|
+
// Overwrite formatted parameters
|
|
1909
|
+
...requestInput.raw
|
|
1910
|
+
};
|
|
1911
|
+
};
|
|
1912
|
+
/**
|
|
1913
|
+
* REST trataPeticion input formatter
|
|
1914
|
+
*
|
|
1915
|
+
* @public
|
|
1916
|
+
*/
|
|
1917
|
+
const restTrataPeticionInputFormatter = (raw) => {
|
|
1918
|
+
const { emv3ds, ...requestInput } = raw;
|
|
1919
|
+
return {
|
|
1920
|
+
...requestInputFormatter(requestInput),
|
|
1921
|
+
...(emv3ds != null ? { DS_MERCHANT_EMV3DS: emv3ds } : undefined),
|
|
1922
|
+
// Overwrite formatted parameters
|
|
1923
|
+
...requestInput.raw
|
|
1924
|
+
};
|
|
1925
|
+
};
|
|
1926
|
+
|
|
1927
|
+
dayjs__default['default'].extend(timezone__default['default']);
|
|
1928
|
+
dayjs__default['default'].extend(utc__default['default']);
|
|
1929
|
+
const formatCountry = (rawCountry) => {
|
|
1930
|
+
const countryInt = Number.parseInt(rawCountry).toString();
|
|
1931
|
+
return REV_COUNTRIES[countryInt];
|
|
1932
|
+
};
|
|
1933
|
+
const formatCardBrand = (rawCardBrand) => {
|
|
1934
|
+
const cardBrandInt = Number.parseInt(rawCardBrand).toString();
|
|
1935
|
+
return REV_CARDBRANDS[cardBrandInt];
|
|
1936
|
+
};
|
|
1937
|
+
const formatResponse = (rawResponse) => Number.parseInt(rawResponse);
|
|
1938
|
+
const baseOutputFormatter = (raw) => {
|
|
1939
|
+
const { Ds_MerchantCode: merchantCode, Ds_Terminal: terminal, Ds_Order: order, Ds_SecurePayment: rawSecurePayment, Ds_TransactionType: transactionType, Ds_Card_Country: rawCardCountry, Ds_Card_Brand: rawCardBrand, Ds_Card_PSD2: rawCardPSD2, Ds_MerchantData: merchantData, Ds_Card_Type: cardType, Ds_AuthorisationCode: authorisationCode } = raw;
|
|
1940
|
+
return {
|
|
1941
|
+
raw,
|
|
1942
|
+
merchantCode,
|
|
1943
|
+
terminal,
|
|
1944
|
+
order,
|
|
1945
|
+
transactionType,
|
|
1946
|
+
...(rawSecurePayment != null && rawSecurePayment.length > 0
|
|
1947
|
+
? { securePayment: rawSecurePayment === '1' || rawSecurePayment === '2' }
|
|
1948
|
+
: undefined),
|
|
1949
|
+
...(isStringNotEmpty(rawCardCountry) ? { cardCountry: formatCountry(rawCardCountry) } : undefined),
|
|
1950
|
+
...(isStringNotEmpty(rawCardBrand) ? { cardBrand: formatCardBrand(rawCardBrand) } : undefined),
|
|
1951
|
+
...(isStringNotEmpty(rawCardPSD2) ? { cardPSD2: rawCardPSD2 === 'Y' } : undefined),
|
|
1952
|
+
...(isStringNotEmpty(merchantData) ? { merchantData } : undefined),
|
|
1953
|
+
...(isStringNotEmpty(cardType) ? { cardType } : undefined),
|
|
1954
|
+
...(isStringNotEmpty(authorisationCode) ? { authorisationCode } : undefined)
|
|
1955
|
+
};
|
|
1956
|
+
};
|
|
1957
|
+
const formatPrice = (params) => {
|
|
1958
|
+
const { Ds_Amount: rawAmount, Ds_Currency: rawCurrency } = params;
|
|
1959
|
+
const currencyInt = Number.parseInt(rawCurrency).toString();
|
|
1960
|
+
const currencyInfo = REV_CURRENCIES[currencyInt];
|
|
1961
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
1962
|
+
if (!currencyInfo) {
|
|
1963
|
+
throw new ParseError('Unknown currency', rawCurrency);
|
|
1841
1964
|
}
|
|
1842
|
-
|
|
1965
|
+
const currency = currencyInfo.code;
|
|
1966
|
+
const amount = new Decimal__default['default'](rawAmount).dividedBy(Math.pow(10, currencyInfo.decimals)).toFixed();
|
|
1967
|
+
return {
|
|
1968
|
+
amount,
|
|
1969
|
+
currency
|
|
1970
|
+
};
|
|
1971
|
+
};
|
|
1972
|
+
const formatLang = (rawLang) => {
|
|
1973
|
+
const langInt = Number.parseInt(rawLang).toString();
|
|
1974
|
+
return REV_LANGUAGES[langInt];
|
|
1975
|
+
};
|
|
1976
|
+
const notificationOutputFormatter = (raw) => {
|
|
1977
|
+
const { Ds_ConsumerLanguage: rawConsumerLanguage, Ds_Response: rawResponse } = raw;
|
|
1978
|
+
return {
|
|
1979
|
+
...baseOutputFormatter(raw),
|
|
1980
|
+
...formatPrice(raw),
|
|
1981
|
+
response: formatResponse(rawResponse),
|
|
1982
|
+
...(isStringNotEmpty(rawConsumerLanguage) ? { lang: formatLang(rawConsumerLanguage) } : undefined)
|
|
1983
|
+
};
|
|
1984
|
+
};
|
|
1985
|
+
/**
|
|
1986
|
+
* REST notification formatter
|
|
1987
|
+
*
|
|
1988
|
+
* @public
|
|
1989
|
+
*/
|
|
1990
|
+
const restNotificationOutputFormatter = (raw) => {
|
|
1991
|
+
const { Ds_Hour: hour, Ds_Date: rawDate } = raw;
|
|
1992
|
+
const date = rawDate.split('/').reverse().join('-');
|
|
1993
|
+
return {
|
|
1994
|
+
...notificationOutputFormatter(raw),
|
|
1995
|
+
time: hour,
|
|
1996
|
+
date,
|
|
1997
|
+
timestamp: dayjs__default['default'].tz(`${date}T${hour}`, 'YYYY-MM-DDTHH:mm', 'Europe/Madrid').toDate()
|
|
1998
|
+
};
|
|
1999
|
+
};
|
|
2000
|
+
/**
|
|
2001
|
+
* SOAP notification formatter
|
|
2002
|
+
*
|
|
2003
|
+
* @public
|
|
2004
|
+
*/
|
|
2005
|
+
const soapNotificationOutputFormatter = (raw) => {
|
|
2006
|
+
const { Hora: hour, Fecha: rawDate } = raw;
|
|
2007
|
+
const date = rawDate.split('/').reverse().join('-');
|
|
2008
|
+
return {
|
|
2009
|
+
...notificationOutputFormatter(raw),
|
|
2010
|
+
time: hour,
|
|
2011
|
+
date,
|
|
2012
|
+
timestamp: dayjs__default['default'].tz(`${date}T${hour}`, 'YYYY-MM-DDTHH:mm', 'Europe/Madrid').toDate()
|
|
2013
|
+
};
|
|
2014
|
+
};
|
|
2015
|
+
const requestOutputFormatter = (raw) => {
|
|
2016
|
+
const { Ds_CardNumber: deprecatedCardNumber, Ds_Card_Number: newCardNumber, Ds_Merchant_Identifier: identifier, Ds_ExpiryDate: rawExpiryDate, Ds_UrlPago2Fases: payURL, Ds_Language: rawLang } = raw;
|
|
2017
|
+
const cardNumber = newCardNumber !== null && newCardNumber !== void 0 ? newCardNumber : deprecatedCardNumber;
|
|
2018
|
+
return {
|
|
2019
|
+
...baseOutputFormatter(raw),
|
|
2020
|
+
...(isStringNotEmpty(cardNumber) ? { cardNumber } : undefined),
|
|
2021
|
+
...(isStringNotEmpty(identifier) ? { identifier } : undefined),
|
|
2022
|
+
...(isStringNotEmpty(payURL) ? { payURL } : undefined),
|
|
2023
|
+
...(isStringNotEmpty(rawExpiryDate)
|
|
2024
|
+
? { expiryYear: rawExpiryDate.slice(0, 2), expiryMonth: rawExpiryDate.slice(2, 4) }
|
|
2025
|
+
: undefined),
|
|
2026
|
+
...(isStringNotEmpty(rawLang) ? { lang: formatLang(rawLang) } : undefined)
|
|
2027
|
+
};
|
|
2028
|
+
};
|
|
2029
|
+
/**
|
|
2030
|
+
* REST iniciaPeticion output formatter
|
|
2031
|
+
*
|
|
2032
|
+
* @public
|
|
2033
|
+
*/
|
|
2034
|
+
const restIniciaPeticionOutputFormatter = (raw) => {
|
|
2035
|
+
const { Ds_EMV3DS: emv3ds } = raw;
|
|
2036
|
+
return {
|
|
2037
|
+
...requestOutputFormatter(raw),
|
|
2038
|
+
...(emv3ds != null ? { emv3ds } : undefined)
|
|
2039
|
+
};
|
|
2040
|
+
};
|
|
2041
|
+
/**
|
|
2042
|
+
* REST trataPeticion output formatter
|
|
2043
|
+
*
|
|
2044
|
+
* @public
|
|
2045
|
+
*/
|
|
2046
|
+
const restTrataPeticionOutputFormatter = (raw) => {
|
|
2047
|
+
const { Ds_Response: rawResponse, Ds_EMV3DS: emv3ds } = raw;
|
|
2048
|
+
return {
|
|
2049
|
+
...requestOutputFormatter(raw),
|
|
2050
|
+
...formatPrice(raw),
|
|
2051
|
+
...(emv3ds != null ? { emv3ds } : undefined),
|
|
2052
|
+
...(rawResponse != null ? { response: Number.parseInt(rawResponse) } : undefined)
|
|
2053
|
+
};
|
|
2054
|
+
};
|
|
1843
2055
|
|
|
2056
|
+
exports.CARDBRANDS = CARDBRANDS;
|
|
2057
|
+
exports.COUNTRIES = COUNTRIES;
|
|
2058
|
+
exports.CURRENCIES = CURRENCIES;
|
|
2059
|
+
exports.GatewayError = GatewayError;
|
|
2060
|
+
exports.HTTPError = HTTPError;
|
|
2061
|
+
exports.LANGUAGES = LANGUAGES;
|
|
1844
2062
|
exports.PRODUCTION_URLS = PRODUCTION_URLS;
|
|
1845
|
-
exports.
|
|
2063
|
+
exports.ParseError = ParseError;
|
|
2064
|
+
exports.REV_CARDBRANDS = REV_CARDBRANDS;
|
|
2065
|
+
exports.REV_COUNTRIES = REV_COUNTRIES;
|
|
2066
|
+
exports.REV_CURRENCIES = REV_CURRENCIES;
|
|
2067
|
+
exports.REV_LANGUAGES = REV_LANGUAGES;
|
|
2068
|
+
exports.RedsysError = RedsysError;
|
|
2069
|
+
exports.ResponseError = ResponseError;
|
|
1846
2070
|
exports.SANDBOX_URLS = SANDBOX_URLS;
|
|
1847
2071
|
exports.TRANSACTION_TYPES = TRANSACTION_TYPES;
|
|
2072
|
+
exports.ValidationError = ValidationError;
|
|
2073
|
+
exports.assertSuccessfulResponseCode = assertSuccessfulResponseCode;
|
|
2074
|
+
exports.create3DSMethodForm = create3DSMethodForm;
|
|
2075
|
+
exports.create3DSv1ChallengeForm = create3DSv1ChallengeForm;
|
|
2076
|
+
exports.create3DSv2ChallengeForm = create3DSv2ChallengeForm;
|
|
2077
|
+
exports.createRedsysAPI = createRedsysAPI;
|
|
2078
|
+
exports.deserializeCres = deserializeCres;
|
|
2079
|
+
exports.deserializeThreeDSMethodData = deserializeThreeDSMethodData;
|
|
1848
2080
|
exports.detectSoapVersion = detectSoapVersion;
|
|
1849
2081
|
exports.getResponseCodeMessage = getResponseCodeMessage;
|
|
1850
2082
|
exports.getSISErrorCodeMessage = getSISErrorCodeMessage;
|
|
2083
|
+
exports.isResponseCodeOk = isResponseCodeOk;
|
|
1851
2084
|
exports.mimicSoap11NotificationResponse = mimicSoap11NotificationResponse;
|
|
1852
2085
|
exports.mimicSoap12NotificationResponse = mimicSoap12NotificationResponse;
|
|
1853
2086
|
exports.mimicSoapNotificationReceiver = mimicSoapNotificationReceiver;
|
|
1854
2087
|
exports.randomTransactionId = randomTransactionId;
|
|
2088
|
+
exports.redirectInputFormatter = redirectInputFormatter;
|
|
2089
|
+
exports.restIniciaPeticionInputFormatter = restIniciaPeticionInputFormatter;
|
|
2090
|
+
exports.restIniciaPeticionOutputFormatter = restIniciaPeticionOutputFormatter;
|
|
2091
|
+
exports.restNotificationOutputFormatter = restNotificationOutputFormatter;
|
|
2092
|
+
exports.restTrataPeticionInputFormatter = restTrataPeticionInputFormatter;
|
|
2093
|
+
exports.restTrataPeticionOutputFormatter = restTrataPeticionOutputFormatter;
|
|
2094
|
+
exports.soapNotificationOutputFormatter = soapNotificationOutputFormatter;
|
|
2095
|
+
exports.useOutputFormatter = useOutputFormatter;
|
|
2096
|
+
exports.usePromiseOutputFormatter = usePromiseOutputFormatter;
|
|
2097
|
+
exports.useSingleInputFormatter = useSingleInputFormatter;
|