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/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
- const TRANSACTION_TYPES = {
507
- AUTHORIZATION: '0',
508
- PRE_AUTHORIZATION: '1',
509
- CONFIRMATION: '2',
510
- AUTO_REFUND: '3',
511
- RECURRING_TRANSACTION: '5',
512
- SUCCESSIVE_TRANSACTION: '6',
513
- PRE_AUTHENTICATION: '7',
514
- PRE_AUTHENTICATION_CONFIRMATION: '8',
515
- PRE_AUTHORIZATION_CANCEL: '9',
516
- DEFERRED_AUTHORIZATION: 'O',
517
- PAYLINK: 'F',
518
- DEFERRED_AUTHORIZATION_CONFIRMATION: 'P',
519
- DEFERRED_AUTHORIZATION_CANCEL: 'Q',
520
- DEFERRED_INITIAL_FEE: 'R',
521
- DEFERRED_SUCCESSIVE_FEE: 'S',
522
- NO_AUTHENTICATION: 'A',
523
- DELETE_REFERENCE: '44' // Eliminar referencia
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 {buffer} buf - Input buffer
532
- * @params {number} blocksize - Size of block
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 {string} key - Key to encrypt message
543
- * @params {string} message - Message to be encrypted
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 getResponseCodeMessage = (code) => {
564
- if ((typeof code !== 'number' && typeof code !== 'string') || !Number.isInteger(code)) {
565
- return null;
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 lookupNum = typeof code === 'string' ? Number.parseInt(code.trim()) : code;
568
- if (!Number.isFinite(lookupNum) || lookupNum < 0) {
569
- return null;
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
- const msg = RESPONSE_CODES[lookupNum.toString()];
572
- if ((msg == null || !msg) && lookupNum < 100) {
573
- return 'Transacción autorizada para pagos y preautorizaciones';
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 msg !== null && msg !== void 0 ? msg : null;
706
+ return order;
576
707
  };
577
- const getSISErrorCodeMessage = (code) => {
578
- if (!code || typeof code !== 'string') {
579
- return undefined;
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
- return SIS_ERROR_CODES[code.trim()];
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
- // Order is important
584
- const signedFieldsXMLResponse = ['Ds_Amount', 'Ds_Order',
585
- 'Ds_MerchantCode', 'Ds_Currency', 'Ds_Response', 'Ds_CardNumber',
586
- 'Ds_TransactionType', 'Ds_SecurePayment'];
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
- const mimicSoap12NotificationResponse = (answer) => {
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
- // ISO 639-1
688
- const LANGUAGES = {
689
- es: '1',
690
- en: '2',
691
- ca: '3',
692
- fr: '4',
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
- // ISO 3166 Alpha 2 => (UN M49 === ISO 3166 Num)
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
- const isStringNotEmpty = (str) => str != null && str.length > 0;
1207
- const inputParamsFormatter = (input, options) => {
1208
- const obj = {};
1209
- for (const key of Object.keys(input)) {
1210
- switch (key) {
1211
- case 'expiryMonth':
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
- dayjs__default['default'].extend(timezone__default['default']);
1554
- dayjs__default['default'].extend(utc__default['default']);
1555
- const hourFormatter = (obj, value) => {
1556
- if (value)
1557
- obj.hour = value;
1558
- };
1559
- const dateFormatter = (obj, value) => {
1560
- // Transform to ISO format
1561
- if (value)
1562
- obj.date = value.split('/').reverse().join('-');
1563
- };
1564
- const langFormatter = (obj, value) => {
1565
- const langInt = Number.parseInt(value);
1566
- const lang = REV_LANGUAGES[langInt];
1567
- if (lang != null && lang)
1568
- obj.lang = lang;
1569
- };
1570
- const responseFormatters = {
1571
- DS_HOUR: hourFormatter,
1572
- HORA: hourFormatter,
1573
- DS_DATE: dateFormatter,
1574
- FECHA: dateFormatter,
1575
- DS_CURRENCY: (obj, value) => {
1576
- const currencyInt = Number.parseInt(value);
1577
- const currency = REV_CURRENCIES[currencyInt];
1578
- if (currency)
1579
- obj.currency = currency.code;
1580
- },
1581
- DS_AMOUNT: (obj, value) => {
1582
- if (value)
1583
- obj.amount = Number.parseInt(value);
1584
- },
1585
- DS_RESPONSE: (obj, value) => {
1586
- // Remove leading zeros
1587
- if (value)
1588
- obj.response = Number.parseInt(value);
1589
- },
1590
- DS_ORDER: (obj, value) => {
1591
- obj.order = value;
1592
- },
1593
- DS_MERCHANTCODE: (obj, value) => {
1594
- obj.merchantCode = value;
1595
- },
1596
- DS_TERMINAL: (obj, value) => {
1597
- obj.terminal = value;
1598
- },
1599
- DS_MERCHANTDATA: (obj, value) => {
1600
- if (value)
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
- const js2xml = new xmlParser.j2xParser({});
1667
- const PRODUCTION_URLS = {
1668
- redirect: 'https://sis.redsys.es/sis/realizarPago',
1669
- ws: 'https://sis.redsys.es/sis/services/SerClsWSEntrada/wsdl/SerClsWSEntrada.wsdl'
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
- const SANDBOX_URLS = {
1672
- redirect: 'https://sis-t.redsys.es:25443/sis/realizarPago',
1673
- ws: 'https://sis-t.redsys.es:25443/sis/services/SerClsWSEntrada/wsdl/SerClsWSEntrada.wsdl'
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
- class Redsys {
1676
- constructor(options) {
1677
- if (!options.secretKey || typeof options.secretKey !== 'string') {
1678
- throw new RedsysError('A secretKey key must be provided');
1679
- }
1680
- if (typeof options.urls !== 'object' || options.urls == null ||
1681
- !options.urls.ws || !options.urls.redirect) {
1682
- throw new RedsysError('URLs must be provided');
1683
- }
1684
- this.secretKey = options.secretKey;
1685
- this.urls = options.urls;
1686
- }
1687
- signOrderText(order, text) {
1688
- return sha256Sign(this.secretKey, order, text);
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
- assertXMLPetitionResponseSignature(data) {
1756
- const operationData = data.OPERACION;
1757
- const signedValues = [];
1758
- for (const field of signedFieldsXMLResponse) {
1759
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-nullish-coalescing
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
- // Processes Web Service Petitions Responses
1778
- processXMLPetitionResponse(xml) {
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
- xmlPetitionParameters(paramsInput, options) {
1784
- const datosEntrada = { DATOSENTRADA: formatParams(paramsInput, options) };
1785
- return js2xml.parse(datosEntrada);
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
- xmlPetitionSignedData(paramsInput, options) {
1788
- const datosEntradaXML = this.xmlPetitionParameters(paramsInput, options);
1789
- const signature = sha256Sign(this.secretKey, paramsInput.order, datosEntradaXML);
1790
- return `<REQUEST>${datosEntradaXML}<DS_SIGNATUREVERSION>HMAC_SHA256_V1</DS_SIGNATUREVERSION><DS_SIGNATURE>${signature}</DS_SIGNATURE></REQUEST>`;
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
- // Good idea unless sessions are used
1793
- async getWSClient() {
1794
- if (!this.wsClient) {
1795
- this.wsClient = soap.createClientAsync(this.urls.ws, {});
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
- async wsPetition(paramsInput, options) {
1801
- const peticion = this.xmlPetitionSignedData(paramsInput, options);
1802
- const client = await this.getWSClient();
1803
- if (!client.trataPeticionAsync) {
1804
- throw new GatewayError('Cannot obtain client');
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
- processSoapNotification(xml) {
1816
- const startToken = '<Request';
1817
- const endToken = '</Request>';
1818
- const startPos = xml.indexOf(startToken);
1819
- const endPos = xml.indexOf(endToken);
1820
- if (startPos < 0 || endPos < 0 || startPos > endPos) {
1821
- throw new ParseError('Cannot find payload of SOAP notification Request', xml);
1822
- }
1823
- const signedStr = xml.slice(startPos, endPos + endToken.length);
1824
- const msg = xmlParser__default['default'].parse(xml, { parseNodeValue: false }).Message;
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
- soapNotificationAnswer(order, bool) {
1837
- const answer = bool ? 'OK' : 'KO';
1838
- const response = `<Response Ds_Version="0.0"><Ds_Response_Merchant>${answer}</Ds_Response_Merchant></Response>`;
1839
- const signature = sha256Sign(this.secretKey, order, response);
1840
- return `<Message>${response}<Signature>${signature}</Signature></Message>`;
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.Redsys = Redsys;
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;