Source: transpayrent.js

/**
 * @copyright Transpayrent ApS 2022
 * @license Common Transpayrent API license
 * @version 1.5.2
 * 
 * @class
 * @classdesc   The Transpayrent SDK simplifies the [strong consumer authentication (SCA)](https://en.wikipedia.org/wiki/Strong_customer_authentication), [payment authorization](https://en.wikipedia.org/wiki/Authorization_hold) and [secure card storage using tokenization](https://en.wikipedia.org/wiki/Tokenization_(data_security)) through the consumer's browser
 * while handling [PCI DSS compliance]{@link https://www.pcisecuritystandards.org/}.
 * The SDK handles the secure communication with Transpayrent's Payment Gateway including automatically retrying requests in case of communication failures and orchestrates the integration into 3 simple steps for the scenarios summarized in the table below.
 * | Authorize Payment | Store Payment Card | 3rd Party Wallet |
 * | ----------------- | ------------------ | ------------------ |
 * | The payment maybe be authorized for a payment transaction using the card details entered by the consumer in the following simple steps:  <ol><li>[Create a new payment transaction]{@link Transpayrent#createTransaction}</li><li>[Authenticate the consumer using 3D Secure or equivalent]{@link Transpayrent#authenticate}</li><li>[Authorize the payment for the payment transaction]{@link Transpayrent#authorize}</li></ol> | The consumer's payment card may be securely stored in Transpayrent's Secure Vault and added to the consumer's wallet in the following simple steps:  <ol><li>[Create a new payment transaction]{@link Transpayrent#createTransaction}</li><li>[Authenticate the consumer using 3D Secure or equivalent]{@link Transpayrent#authenticate}</li><li>[Save the consumer's payment card]{@link Transpayrent#save}</li></ol> | The payment maybe be authorized for a payment transaction using a 3rd party wallet such as Apple Pay, Google Pay or MobilePay Online in the following simple steps:  <ol><li>[Create a new payment transaction]{@link Transpayrent#createTransaction}</li><li>[Initialize Payment with 3rd party]{@link Transpayrent#initialize}</li><li>[Authorize the payment for the payment transaction]{@link Transpayrent#authorize}</li></ol> |
 * 
 * ***Please note that the SDK is intended to communicate directly with Transpayrent's Payment Gateway to handle [PCI DSS compliance]{@link https://www.pcisecuritystandards.org/} for authentication and authorization.***  
 * 
 * The SDK handles the authentication by orchestrating the calls to several methods internally and automatically performing the following actions if required:
 *  * [Fingerprint the consumer's brower]{@link Transpayrent#fingerprint} in an invisible iframe
 *  * [Display an authentication challenge]{@link Transpayrent#displayChallenge} to the consumer in  a Lightbox iframe
 *  * [Display the flow for initializing the payment with a 3rd party wallet]{@link Transpayrent#displayPaymentInitialization} to the consumer in a Lightbox iframe
 * 
 * The *look'n'feel* of the Lightbox for the authentication challenge and payment initialization flow may be fully customized using [CSS](https://en.wikipedia.org/wiki/CSS) simply by passing the appropriate iframe configuration when invoking the SDK's [authenticate]{@link Transpayrent#authenticate} or [initialize]{@link Transpayrent#initialize} methods.  
 * The SDK includes methods for performing each part *(initialization, authentication and verification)* of the strong consumer authentication process but it's strongly recommended to use the [authenticate]{@link Transpayrent#authenticate} to orchestrate the process.  
 * 
 * Additionally the SDK provides several helper methods, which may be used to streamline the user experience for the following scenarios:
 *  * [Determine Transpayrent's Payment Method Id using the entered card number]{@link Transpayrent#getPaymentMethodId}
 *  * [Validate the entered card number]{@link Transpayrent#isCardNumberValid}
 *  * [Validate the entered expiry date]{@link Transpayrent#isExpiryDateValid}
 *  * [Validate the entered Card Verification Value (CVV)]{@link Transpayrent#isCVVValid}
 *  * [Determine whether the payment transaction is exempt from Strong Consumer Authentication (SCA)]{@link Transpayrent#isSCAExempt}
 *  * [Retrieve Transpayrent's status message for the specified status code]{@link Transpayrent#getStatusMessage}
 * 
 * The code sample below provides a complete illustration of how the payment page should use the SDK to create a new payment transaction, authenticate the consumer, securely store the payment card and authorize the payment using the stored card for a payment transaction.  
 * The same flow may also be used to complete the payment for a payment transaction using a 3rd party wallet such as Apple Pay, Google Pay or MobilePay Online.  
 * Please note that the sample assumes a payment session has already been created.
 * <font size="-2">
 * ```
 *  <script src="https://storage.googleapis.com/static.[ENVIRONMENT].transpayrent.cloud/v1/swagger-client.js"></script>
 *  <script src="https://storage.googleapis.com/static.[ENVIRONMENT].transpayrent.cloud/v1/transpayrent.js"></script>
 *  <script>
 *      var transpayrentConfig = {
 *          merchantId: [UNIQUE MERCHANT ID ASSIGNED BY TRANSPAYRENT],
 *          sessionId: [ID IN THE RESPONSE FROM "Create Payment Session"],
 *          accessToken: '[x-transpayrent-access-token HTTP HEADER IN THE RESPONSE FROM "Create Payment Session"]'
 *      };
 *      var url = 'https://generator.[ENVIRONMENT].transpayrent.cloud/v1/'+ transpayrentConfig.merchantId +'/system/PAYMENT_GATEWAY/sdk/CLIENT';
 * 
 *      var card = { card_number: 4111111111111111,     // Successful Authorization: Manual Challenge with browser fingerprint
 *                   expiry_month: 9,
 *                   expiry_year: 22,
 *                   cvv: 987,
 *                   card_holder_name: 'John Doe',
 *                   save: true };
 *      var address = { street : 'Arne Jacobsens Allé 7',
 *                      appartment : '5. sal',
 *                      city : 'Copenhagen S',
 *                      postal_code : '2300',
 *                      state : '82',
 *                      country: 208 };                 // Denmark
 *      var phone = { international_dialing_code: 45,   // Denmark
 *                    phone_number: 89671108 };
 *      var email = 'hello@transpayrent.dk';
 *  
 *      var sdk = new Transpayrent(url, transpayrentConfig);
 *      card.payment_method_id = sdk.getPaymentMethodId(card.card_number);
 *      var createTransactionRequests = new Array();
 *      // Card based payment
 *      createTransactionRequests[card.payment_method_id] = { correlation_id : 'TP-'+ transpayrentConfig.sessionId,
 *                                                            amount: { currency : 208,          // DKK
 *                                                                      value : card.save ? 0 : 100 } };
 *      // Transpayrent - Consumer Wallet
 *      createTransactionRequests[201] = { correlation_id : 'TP-'+ transpayrentConfig.sessionId,
 *                                         amount: { currency : 208,          // DKK
 *                                                   value : 100 } };
 *      // MobilePay Online
 *      createTransactionRequests[202] = { correlation_id : 'TP-'+ transpayrentConfig.sessionId,
 *                                         amount: { currency : 208,          // DKK
 *                                                   value : 100 } };
 *      
 *      var initializePaymentRequests = new Array();
 *      // Card based payment
 *      initializePaymentRequests[card.payment_method_id] = { payment_method_id : card.payment_method_id }
 *      // Consumer Wallet
 *      initializePaymentRequests[201] = { payment_method_id : 201 }
 *      // MobilePay Online
 *      initializePaymentRequests[202] = { payment_method_id : 202,
 *                                         mobile : phone,
 *                                         save : false };
 *      
 *      var authenticateConsumerRequests = new Array();
 *      // Card based payment
 *      authenticateConsumerRequests[card.payment_method_id] = { payment_details : card,
 *                                                               billing_address : address,
 *                                                               shipping_address : address,
 *                                                               contact : { mobile : phone,
 *                                                                           work : phone,
 *                                                                           home : phone,
 *                                                                           email : email } };
 *      
 *      var authorizePaymentRequests = new Array();
 *      // Card based payment
 *      authorizePaymentRequests[card.payment_method_id] = card;
 *      // Consumer Wallet
 *      authorizePaymentRequests[201] = { payment_method_id : 201,
 *                                        valuable_id : [VALUEABLE ID IN THE RESPONSE FROM "Create Payment Session"],
 *                                        access_token : '[MERCHANT'S SINGLE SIGN-ON TOKEN FOR AUTHORIZING A PAYMENT WITH THE CONSUMER'S WALLET]' };
 *      // MobilePay Online
 *      authorizePaymentRequests[202] = { payment_method_id : 202 };
 * 
 *      var saveCardRequest = { payment_details : card,
 *                              consumer_id : '[MERCHANT'S CONSUMER ID FROM PAYMENT SESSION]',
 *                              name : "My VISA Card" }
 *      
 *      var paymentMethodId = 202;  // CHANGE THIS TO CONTROL WHICH PAYMENT METHOD IS USED
 * 
 *      // Create payment transaction using the specified payment method
 *      sdk.createTransaction(paymentMethodId, createTransactionRequests[paymentMethodId])
 *      .then(
 *          transaction => {
 *              // Creation of Payment Transaction failed - Display status message
 *              if (transaction.status) {
 *                  alert('API: '+ transaction.api +' failed with HTTP Status Code: '+ transaction.status +' and error: '+ transaction.messages[0].message +'('+ transaction.messages[0].code +')');
 *                  return Promise.reject(null);
 *              }
 *              else {
 *                  var iframeConfig = { container : document.getElementById('container'),
 *                                       css: 'initialization',
 *                                       callback : function (event, iframe) {
 *                                           switch (event) {
 *                                               case 'payment-initialization-initiated':
 *                                                  document.getElementById('loader').style.visibility = 'hidden';
 *                                                  break;
 *                                               case 'payment-initialization-completed':
 *                                                  document.getElementById('loader').style.visibility = 'inherit';
 *                                                  break;
 *                                           }
 *                                        } };
 *                  return sdk.initialize(transaction.id, initializePaymentRequests[paymentMethodId], iframeConfig);
 *              }
 *          })
 *        .then(
 *          initialization => {
 *               // Initialization of Payment Transaction failed - Display status message
 *              if (initialization.status) {
 *                  alert('API: '+ initialization.api +' failed with HTTP Status Code: '+ initialization.status +' and error: '+ initialization.messages[0].message +'('+ initialization.messages[0].code +')');
 *                  return Promise.reject(null);
 *              }
 *              // Payment Transaction is exempt from Strong Consumer Authentication (SCA)
 *              else if (sdk.isSCAExempt(paymentMethodId, createTransactionRequests[paymentMethodId].amount, card.save) ) {
 *                  return Promise.resolve(initialization);
 *              }
 *              else {
 *                  var iframeConfig = { container : document.getElementById('container'),
 *                                       css: 'challenge',
 *                                       callback : function (event, iframe) {
 *                                           switch (event) {
 *                                               case 'authentication-challenge-initiated':
 *                                                  document.getElementById('loader').style.visibility = 'hidden';
 *                                                  break;
 *                                               case 'authentication-challenge-completed':
 *                                                  document.getElementById('loader').style.visibility = 'inherit';
 *                                                  break;
 *                                           }
 *                                       } };
 *                  return sdk.authenticate(initialization.transaction_id, authenticateConsumerRequests[paymentMethodId], iframeConfig);
 *              }
 *      })
 *      .then(
 *          authentication => {
 *              // Consumer Authentication failed
 *              if (authentication.status) {
 *                  // Consumer failed authentication challenge
 *                  if (authentication.status == 511) {
 *                      alert('Consumer failed authentication challenge for payment transaction: '+ authentication.transaction_id +' with error: '+ sdk.getStatusMessage(authentication.status_code) +' ('+ authentication.status_code +')');
 *                  }
 *                  // API request failed - Display status message
 *                  else {
 *                      alert('API: '+ authentication.api +' failed with HTTP Status Code: '+ authentication.status +' and error: '+ authentication.messages[0].message +'('+ authentication.messages[0].code +')');
 *                  }
 *                  return Promise.reject(null);
 *              }
 *              // Payment Transaction is exempt from Strong Consumer Authentication (SCA)
 *              else if (sdk.isSCAExempt(paymentMethodId, createTransactionRequests[paymentMethodId].amount, card.save) ) {
 *                  return sdk.authorize(authentication.transaction_id, card);
 *              }
 *              // Consumer Authentication succesfully completed: Save Payment Card
 *              else if (card.save) {
 *                  saveCardRequest.card.cryptogram = authentication.cryptogram;
 *                  return sdk.save(authentication.transaction_id, saveCardRequest);
 *              }
 *              // Consumer Authentication succesfully completed: Authorize Payment
 *              else {
 *                  card.cryptogram = authentication.cryptogram;
 *                  return sdk.authorize(authentication.transaction_id, card);
 *              }
 *          })
 *      .then(
 *          save => {
 *              // Payment Card attempted saved
 *              if (100 < paymentMethodId && paymentMethodId < 200 && card.save) {
 *                  // Saving Payment Card failed - Display status message
 *                  if (save.status) {
 *                      alert('API: '+ save.api +' failed with HTTP Status Code: '+ save.status +' and error: '+ save.messages[0].message +'('+ save.messages[0].code +')');
 *                      return Promise.reject(null);
 *                  }
 *                  // Payment card succesfully saved and added to the consumer's wallet
 *                  else {
 *                      var request = { payment_method_id: 201,            // Consumer Wallet
 *                                      valuable_id : save.valuable_id,
 *                                      access_token : authorizePaymentRequests[201].access_token }
 *                  
 *                      // Create new transaction for authorizing a payment with the stored card
 *                      return sdk.createTransaction(request.payment_method_id, createTransactionRequests[request.payment_method_id])
 *                          .then(
 *                              transaction => {
 *                                  // Creation of Payment Transaction failed - Display status message
 *                                  if (transaction.status) {
 *                                      alert('API: '+ transaction.api +' failed with HTTP Status Code: '+ transaction.status +' and error: '+ transaction.messages[0].message +'('+ transaction.messages[0].code +')');
 *                                      return Promise.reject(null);
 *                                  }
 *                                  // Authorize the payment for the payment transaction using the consumer's stored card
 *                                  else {
 *                                      return sdk.authorize(transaction.id, request);
 *                                  }
 *                              });
 *                  }
 * 
 *              }
 *              // Payment authorization
 *              else {
 *                  return Promise.resolve(save);
 *              }
 *          })
 *      .then(
 *          authorization => {
 *              // Payment Authorization failed - Display status message
 *              if (authorization.status) {
 *                  alert('API: '+ authorization.api +' failed with HTTP Status Code: '+ authorization.status);
 *              }
 *              // Payment Authorization completed - Display success message
 *              else {
 *                  alert('Payment transaction: '+ authorization.transaction_id +' successfully authorized');
 *              }
 *          })
 *      .catch(reason => {
 *          // Low level error - Display error message
 *          if (reason) {
 *              console.error(reason);
 *          }
 *      });
 *  </script>
 * ```
 * </font>
 * 
 * @see [SwaggerClient] {@link https://github.com/swagger-api/swagger-js#swagger-client-}
 * 
 * @constructor
 * @description Creates a new instance of the Transpayrent SDK, which simplifies the strong consumer authentication (SCA) and payment authorization through the consumer's browser.
 * @example <caption>Instantiate the Transpayrent SDK</caption>
 *  <script src="https://storage.googleapis.com/static.[ENVIRONMENT].transpayrent.cloud/v1/swagger-client.js"></script>
 *  <script src="https://storage.googleapis.com/static.[ENVIRONMENT].transpayrent.cloud/v1/transpayrent.js"></script>
 *  <script>
 *      var transpayrentConfig = {
 *          merchantId: [UNIQUE MERCHANT ID ASSIGNED BY TRANSPAYRENT],
 *          sessionId: [ID IN THE RESPONSE FROM "Create Payment Session"],
 *          accessToken: '[x-transpayrent-access-token HTTP HEADER IN THE RESPONSE FROM "Create Payment Session"]'
 *      };
 *      var url = 'https://generator.[ENVIRONMENT].transpayrent.cloud/v1/'+ transpayrentConfig.merchantId +'/system/PAYMENT_GATEWAY/sdk/CLIENT';
 *      var sdk = new Transpayrent(url, transpayrentConfig);
 *  </script>
 * 
 * @param {String} url           The URL pointing to the Payment Gateway's OpenAPI service definitions.
 * @param {BaseConfig} config    The base configuration for the API requests.
 */
function Transpayrent(url, config) {
    /**
     * Max number of attempts for communicating with one of the Payment Gateway APIs.
     *
     * @private
     *
     * @type {Integer}
     */
     const MAX_ATTEMPTS = 5;
     /**
      * The base time in milliseconds between each communication attempt.
      * The actual time is between each attempt is calculated as attempt * TIME.
      * 
      * @see {@link Transpayrent#sleep}
      *
      * @private
      *
      * @type {Integer}
      */
     const TIME = 1000;
     /**
      * Amount thresholds for Strong  Consumer Authentication (SCA) using 3D Secure or equivalent
      * 
      * @see {@link Transpayrent#isSCAExempt}
      *
      * @private
      *
      * @type {Map}
      */
     const SCA_AMOUNTS = { 208 : 22000, // DKK
                           978 : 3000,  // EUR
                           826 : 2500,  // GBP
                           840 : 3500,  // USD
                           752 : 32500, // SEK
                           578 : 31500, // NOK
                           191 : 22500, // HRK
                           986 : 0      // BRL
                         };

    /**
     * Base configuration for all requests made to the Payment Gateway APIs.
     * 
     * @typedef {Object} BaseConfig
     * @property {Integer} merchantId   Transpayrent's unique ID for the merchant.
     * @property {Long} sessionId       Transpayrent's unique ID for the payment session that was returned as the `id` property in the response from the "Create Payment Session" API.
     * @property {String} accessToken   The access token for the payment session that was returned in the HTTP Header: `x-transpayrent-access-token` in the response from the "Create Payment Session" API.
     */
    /**
     * The base configuration for the API requests.
     *
     * @private
     *
     * @type {BaseConfig}
     */
    this._config = config;
    /**
     * List of statuses using the status code as the key and the status message as the value.
     *
     * @private
     *
     * @type {Array}
     */
    this._statuses = new Array();
    /**
     * List of countries using the numeric country code as the key and the alpha-2 code as the value
     *
     * @private
     *
     * @type {Array}
     */
    this._countries = new Array();
    /**
     * List of currencies using the numeric currencies code as the key and the alpha-3 code as the value
     *
     * @private
     *
     * @type {Array}
     */
    this._currencies = new Array();
    /**
     * List of cached transaction requests for specific payment methods such as Apple Pay or Google Pay using the {@link Transpayrent#PAYMENT_METHOD_ID}
     * as the key and the request body for {@link Transpayrent#createTransaction} as the value.
     *
     * @private
     *
     * @type {Array}
     */
    this._transactionRequestCache = new Array();
    /**
     * List of cached transaction results for specific payment methods such as Apple Pay or Google Pay using the {@link Transpayrent#PAYMENT_METHOD_ID}
     * as the key and the response body for {@link Transpayrent#createTransaction} as the value.
     *
     * @private
     *
     * @type {Array}
     */
    this._transactionResponseCache = new Array();
    /**
     * List of cached payment initializations for specific payment transaction with a 3rd party wallet such as Apple Pay or Google Pay using the transactionId
     * as the key and the return value from {@link Transpayrent#initialize} as the value.
     *
     * @private
     *
     * @type {Array}
     */
    this._initializationCache = new Array();
    /**
     * List of cached tokens for specific payment methods such as Apple Pay or Google Pay using the {@link Transpayrent#PAYMENT_METHOD_ID}
     * as the key and the payment token returned by the payment method as the value.
     *
     * @private
     *
     * @type {Array}
     */
    this._tokenCache = new Array();

    this._config.url = url;

    /**
     * Instantiates a new Swagger Client using OpenAPI specification from the provided URL.
     * 
     * @private
     * 
     * @param {String} url          The URL pointing to the Payment Gateway's OpenAPI service definitions.
     * @param {Integer} attempt     The current attempt at creating the payment transaction, defaults to 1
     * @returns                     The instantiated Swagger Client used to interact with the Payment Gateway API's defined using [Open API]{@link https://www.openapis.org}
     */
    Transpayrent.prototype.newClient = function (url, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        return new SwaggerClient(url, { requestInterceptor: (request) => {
                                            if (request.loadSpec) {
                                                request.headers['cache-control'] = `max-age=${60*60*24}`;
                                            }
                                            else {
                                                request.headers['authorization'] = 'Bearer '+ this._config.accessToken;
                                            }
                                        },
                                        responseInterceptor: (response) => {
                                            if (response.ok) {
                                                // Update the API access token the refreshed token returned by the server
                                                if (response.headers['x-transpayrent-access-token']) {
                                                    this._config.accessToken = response.headers['x-transpayrent-access-token'];
                                                }
                                                // Parse status codes and status messages
                                                else if (response.url.endsWith("status-code_v1.yaml") ) {
                                                    for (var i=0; i<response.body.STATUS_CODE.enum.length; i++) {
                                                        this._statuses[response.body.STATUS_CODE.enum[i] ] = response.body.STATUS_CODE['x-enum-descriptions'][i];
                                                    }
                                                }
                                                // Parse countries
                                                else if (response.url.endsWith("country_v1.yaml") ) {
                                                    for (var i=0; i<response.body.COUNTRY.enum.length; i++) {
                                                        this._countries[response.body.COUNTRY.enum[i] ] = response.body.COUNTRY['x-enum-varnames'][i];
                                                    }
                                                }
                                                // Parse currencies
                                                else if (response.url.endsWith("currency_v1.yaml") ) {
                                                    for (var i=0; i<response.body.CURRENCY.enum.length; i++) {
                                                        this._currencies[response.body.CURRENCY.enum[i] ] = response.body.CURRENCY['x-enum-varnames'][i];
                                                    }
                                                }
                                            }
                                            // API request failed
                                            else if (this._config.url != response.url) {
                                                var body = response.body;
                                                if (Array.isArray(body) == false) {
                                                    body = new Array(body);
                                                }
                                                if (body[0].system || body[0].status_code) {
                                                    response.ok = true;
                                                }
                                            }
                                        }
        })
        .catch(reason => {
            if (attempt < MAX_ATTEMPTS) {
                return this.sleep(attempt * TIME)
                    .then(r => this.newClient(url, attempt + 1) );
            }
            else {
                console.error(`Failed to instantiate Swagger Client using url: ${url} due to: ${reason}`);
                return Promise.reject(reason);
            }
        });
    };
    /**
     * The Swagger Client that was generated from the provided URL.
     * The client is used to interact with the Payment Gateway APIs defined using [Open API]{@link https://www.openapis.org}
     *
     * @private
     *
     * @type {SwaggerClient}
     */
    this._client = this.newClient(url);
    
                                        
    /**
     * A representation of a status message returned by the Transpayrent Payment Gateway.
     * 
     * @see [StatusMessage](/v1/model/common/status-message/status-message_v1.yaml#/StatusMessage)
     * @see [SYSTEM](/v1/model/common/status-message/system_v1.yaml#/SYSTEM)
     * @see [STATUS_CODE](/v1/model/common/status-message/status-code_v1.yaml#/STATUS_CODE)
     * 
     * @typedef {Object} StatusMessage
     */
    /**
     * A status response returned by the Payment Gateway when an API request fails.
     * 
     * @typedef {Object} Status
     * @property {String} api               The Payment Gateway API that returned the message(s).
     * @property {Integer} status           The HTTP Status Code returned by the Payment Gateway.
     * @property {Array.<StatusMessage>} messages A list of messages returned by the Payment Gateway.
     */
    /**
     * Normalizes the status message(s) returned by the Transpayrent Payment Gateway into an object with the following properties:
     * 
     * @private
     * 
     * @param {String} api              The Payment Gateway API that returned the message(s).
     * @param {Integer} status          The HTTP Status Code returned by the Payment Gateway.
     * @param {Any} messages            A single message or a list of messages returned by the Payment Gateway.
     * @param {Integer} transactionId   The unique id of the payment transaction for which the message(s) are applicable
     * @returns {Status} A normalized object with a list of status message(s) returned by the Payment Gateway
     */
    Transpayrent.prototype._normalizeStatus = function (api, status, messages, transactionId) {
        return { api : api,
                 status : status,
                 transaction_id : transactionId,
                 messages : Array.isArray(messages) ? messages : new Array(messages) };
    };

    /**
     * Constructs an iframe which sends an HTTP POST request to enable the Access Control Server (ACS) to fingerprint the consumer's browser.
     * 
     * @public
     * 
     * @param {String} url      The URL to the Access Control Server (ACS), which will create a fingerprint of the consumer's browser as part of the 3D Secure process for strong consumer authentication.
     * @param {String} data     The base64 encoded request data that must be sent to the Access Control Server (ACS).
     *                          The data will be sent to the ACS in the `threeDSMethodData` field for 3D Secure v2 and in the `pareq` field for 3D Secure v1.
     * @returns {HTMLIFrameElement} The constructed iframe element
     */
    Transpayrent.prototype.fingerprint = function (url, data) {
        var iframe = document.createElement('iframe');
        iframe.style.display = 'none';
        iframe.style.visibility = 'hidden';
        iframe.setAttribute('id', '3ds-method-iframe');
        iframe.setAttribute('name', '3ds-method-iframe');
        document.body.appendChild(iframe);
        var form = document.createElement('form');
        form.setAttribute('id', '3ds-method-form');
        form.setAttribute('name', '3ds-method-form');
        form.setAttribute('method', 'post');
        form.setAttribute('target', '3ds-method-iframe');
        form.setAttribute('action', url);
        var input = document.createElement('input');
        input.setAttribute('type', 'hidden');
        input.setAttribute('id', 'threeDSMethodData');
        input.setAttribute('name', 'threeDSMethodData');
        input.setAttribute('value', data);
        form.appendChild(input);
        document.body.appendChild(form);
        form.submit();
        
        return iframe;
    };
    
    /**
     * Configuration for the constructed iframe element which will be used to display the following:
     *  - authentication challenge
     *  - payment initialization
     * 
     * @example <caption>Example callback function</caption>
     *  function (event, iframe) {
     *      switch (event) {
     *          case 'authentication-challenge-initiated':
     *              // DO SOMETHING BEFORE THE AUTHENTICATION CHALLENGE IS DISPLAYED
     *              break;
     *          case 'authentication-challenge-completed':
     *              // DO SOMETHING AFTER THE AUTHENTICATION CHALLENGE IS COMPLETE
     *              break;
     *          case 'payment-initialization-initiated':
     *              // DO SOMETHING BEFORE THE INITIALIZATION OF THE PAYMENT FOR THE PAYMENT TRANSACTION IS DISPLAYED
     *              break;
     *          case 'payment-initialization-completed':
     *              // DO SOMETHING AFTER INITIALIZATION OF THE PAYMENT FOR THE PAYMENT TRANSACTION IS COMPLETE
     *              break;
     *  }
     * 
     * @typedef {Object} IFrameConfig
     * @property {Element} container    The container element in which the constructed iframe will be displayed. Defaults to `document.body`.
     * @property {String} css           Optional CSS class(es) that will be applied to the constructed iframe.
     * @property {String} callback      An optional callback function which will be invoked just before the authentication challenge is initiated and just after the challenge is completed.
     *                                  The callback function accepts 2 arguments:  
     *                                      - `event`: The event that triggered the callback, will be either `authentication-challenge-initiated`, `authentication-challenge-completed`, `payment-initialization-initiated` or `payment-initialization-completed`  
     *                                      - `iframe`: The constructed iframe that is used for the authentication challenge or payment initialization
     */
    /**
     * Constructs the iframe element for displaying the authentication challenges and attaches it inside the provided container element.
     * 
     * @public
     * 
     * @param {String} url          The URL to the Access Control Server (ACS) will use for the challenge during the strong consumer authentication (SCA) process.
     * @param {String} data         The base64 encoded request data that must be sent to the Access Control Server (ACS).
     *                              The data will be sent to the ACS in the `creq` field.
     * @param {IFrameConfig} config The configuration for the constructed iframe element
     * @returns {HTMLIFrameElement} The constructed iframe element
     */
    Transpayrent.prototype.displayChallenge = function (url, data, config) {
        if (!config.container) {
            config.container = document.body;
            this.clearContainer(config.container);
        }
        else {
            this.clearContainer(config.container);
            config.container.style.visibility = 'visible';
        }
        var iframe = document.createElement('iframe');
        if (config.css) {
            iframe.className = config.css;
        }
        iframe.setAttribute('id', '3ds-challenge-iframe');
        iframe.setAttribute('name', '3ds-challenge-iframe');
        config.container.appendChild(iframe);
        if (config.callback) {
            config.callback('authentication-challenge-initiated', iframe);
        }
        var form = document.createElement('form');
        form.setAttribute('id', '3ds-challenge-form');
        form.setAttribute('name', '3ds-challenge-form');
        form.setAttribute('method', 'post');
        form.setAttribute('target', '3ds-challenge-iframe');
        form.setAttribute('action', url);
        var input = document.createElement('input');
        input.setAttribute('type', 'hidden');
        input.setAttribute('id', 'creq');
        input.setAttribute('name', 'creq');
        input.setAttribute('value', data);
        form.appendChild(input);
        input = document.createElement('input');
        input.setAttribute('type', 'hidden');
        input.setAttribute('id', 'threeDSSessionData');
        input.setAttribute('name', 'threeDSSessionData');
        form.appendChild(input);
        document.body.appendChild(form);
        form.submit();

        return iframe;
    };
    /**
     * Constructs the iframe element for the consumer to initialize the payment for the payment transaction with an upstream 3rd party provider such as Apple Pay, Google Pay or MobilePay Online.
     * The method will trigger a callback to the provided callback if defined sending a `payment-initialization-initiated` event.
     * 
     * @public
     * 
     * @param {PAYMENT_METHOD_ID} paymentMethodId       The unique id of the payment method which will be used to initialize the payment for the payment transaction.
     * @param {String} url                              The URL to the upstream 3rd party provider with which the consumer must initialize the payment for the payment transaction
     * @param {Integer} method                          The HTTP method which is required by the upstream 3rd party provider to initialize the payment for the payment transaction
     * @param {String} data                             A map of data which will be sent to the upstream 3rd party provider as name value pairs to initialize the payment for the payment transaction
     * @param {IFrameConfig} config                     The configuration for the constructed iframe element
     * @returns {HTMLIFrameElement|HTMLDivElement}      The constructed lightbox element
     */
     Transpayrent.prototype.displayPaymentInitialization = function (paymentMethodId, url, method, data, config) {
        if (!config.container) {
            config.container = document.body;
            this.clearContainer(config.container);
        }
        else {
            this.clearContainer(config.container);
            config.container.style.visibility = 'visible';
        }
        var lightbox = document.createElement('iframe');
        lightbox.setAttribute('name', 'payment-initialization-lightbox');
        // DATA
        if (method == 4) {
            lightbox = document.createElement('div');
        }
        if (config.css) {
            lightbox.className = config.css +` payment-method-${paymentMethodId}`;
        }
        else {
            lightbox.className = `payment-method-${paymentMethodId}`;
        }
        lightbox.setAttribute('id', 'payment-initialization-lightbox');
        config.container.appendChild(lightbox);
        switch (method) {
            case 1: // GET
                if (data) {
                    url += '?'+ Object.keys(data).map(name => {
                        return `${name}=${encodeURIComponent(data[name])}`;
                    })
                    .join('&');
                }
                lightbox.setAttribute('src', url);
                break;
            case 2: // POST
            case 3: // PUT
                var form = document.createElement('form');
                form.setAttribute('id', 'payment-initialization-form');
                form.setAttribute('name', 'payment-initialization-form');
                form.setAttribute('method', 'post');
                form.setAttribute('target', 'payment-initialization-iframe');
                form.setAttribute('action', url);
                if (data) {
                    for (var name in data) {
                        var input = document.createElement('input');
                        input.setAttribute('type', 'hidden');
                        input.setAttribute('id', name);
                        input.setAttribute('name', name);
                        input.setAttribute('value', data[name]);
                        form.appendChild(input);
                    }
                }
                document.body.appendChild(form);
                form.submit();
                break;
            case 4: // DATA
                // Barcode
                var img = document.createElement('img');
                img.src = `data:image/png;charset=utf-8;base64,${data.barcode}`
                img.className = 'barcode';
                lightbox.appendChild(img);
                // Barcode number
                var div = document.createElement('div');
                div.className = 'barcode-number';
                div.innerText = data.number;
                lightbox.appendChild(div);
                var button = document.createElement('button');
                button.type = 'button';
                button.className = 'barcode-number';
                var sdk = this;
                button.onclick = function () {
                    sdk.copyToClipboard(data.number);
                }
                button.innerText = `Copia e Cola`
                lightbox.appendChild(button);
                // Expiry time
                div = document.createElement('div');
                div.className = 'barcode-expiry';
                var label = document.createElement('label');
                label.innerText = `PIX expira em `;
                div.appendChild(label);
                var span = document.createElement('span');
                span.id = 'barcode-expiry';
                span.setAttribute('data-expiry', data.expiry);
                span.innerText = new Date(new Date(data.expiry) - Date.now() ).toISOString().slice(11, 19);
                div.appendChild(span);
                lightbox.appendChild(div);
                // Count down script
                var script = document.createElement('script');
                script.innerHTML = `var _handler = function () {
                    if (document.getElementById('barcode-expiry') ) {
                        const expiry = new Date(document.getElementById('barcode-expiry').getAttribute('data-expiry') );
                        const remaining = parseInt( (expiry - Date.now() ) / 1000);
                        document.getElementById('barcode-expiry').innerText = new Date(expiry - Date.now() ).toISOString().slice(11, 19);
                        if (remaining > 0) {
                            setTimeout(_handler, 1000);
                        }
                        else {
                            window.parent.postMessage('payment-initialization-expired', '*');
                        }
                    }
                }
                setTimeout(_handler, 1000);`;
                lightbox.appendChild(script);
                break;
        }
        if (config.callback) {
            config.callback('payment-initialization-initiated', lightbox);
        }

        return lightbox;
    };
    /**
     * Copies the specified text to the clipboard using the {@link https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard|Clipboard API}.  
     * The method will gracefully fallback to the deprecated {@link https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand|execCommand function}.
     * 
     * @param {String} text     The text that will be copied to the clipboard
     */
    Transpayrent.prototype.copyToClipboard = function (text) {
        try {
            // navigator clipboard api needs a secure context (https or localhost)
            if (navigator.clipboard && window.isSecureContext) {
                navigator.clipboard.writeText(text);
            }
            else {
                var input = document.createElement('input');
                input.value = text;
                // place the input outside the viewport
                input.style.position = 'fixed';
                input.style.left = "-999999px";
                input.style.top = "-999999px";
                document.body.appendChild(input);
                input.focus();
                input.select();
                document.execCommand('copy');
                input.remove();
            }
        }
        catch (e) {
            console.log(`Unable to copy text to clipboard due to error: ${e.message}`);
        }
    }
    /**
     * Clears the specified container of child elemens with the following well-known ids:  
     *  - payment-initialization-lightbox  
     *  - 3ds-challenge-iframe  
     * 
     * @private
     * 
     * @param {Element} container   The container from which the child elemens with well-known ids should be removed
     */
    Transpayrent.prototype.clearContainer = function (container) {
        const knownIds = [ 'payment-initialization-lightbox', '3ds-challenge-iframe' ];
        var removals = [];
        for (const child of container.children) {
            if (knownIds.includes(child.id) ) {
                removals.push(child);
            }
        }
        for (const element of removals) {
            element.remove();
        }
    }

    /**
     * Sleep for the specified number of milliseconds.
     * 
     * @private
     * 
     * @example <caption>Sleep for 1 second</caption>
     *  this.sleep(1000)
     *      .then(r => {
     *          // DO STUFF
     *      });
     * 
     * @param {Integer} time 
     * @returns A resolved Promise
     */
    Transpayrent.prototype.sleep = function (time) {
        return new Promise(resolve => setTimeout(resolve, time) );
    }

    /**
     * The unique id for the payment method (AMEX, MasterCard, VISA etc.).
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [PAYMENT_METHOD_ID](/v1/model/payment-gateway/payment-method/payment-method-id_v1.yaml#/PAYMENT_METHOD_ID)
     * 
     * @enum
     * @typedef {Enum} PAYMENT_METHOD_ID
     */
    /**
     * A representation of the consumer's payment transaction.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [CreatePaymentTransactionRequest](/v1/model/payment-gateway/payment-transaction/payment-transaction_v1.yaml#/CreatePaymentTransactionRequest)
     * @see [Amount](/v1/model/common/amount/amount_v1.yaml#/Amount)
     * @see [CURRENCY](/v1/model/common/currency/currency_v1.yaml#/CURRENCY)
     * 
     * @typedef {Object} CreatePaymentTransactionRequest
     */
    /**
     * A simplified representation of the created payment transaction.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [CreatePaymentTransactionResponse](v1/model/payment-gateway/payment-transaction/payment-transaction_v1.yaml#/CreatePaymentTransactionResponse)
     * @see [RecordEntityId](/v1/model/common/record-entity-id/record-entity-id_v1.yaml#/RecordEntityId)
     * 
     * @typedef {Object} CreatePaymentTransactionResponse
     */
    /**
     * Creates a new payment transaction through the Transpayrent Payment Gateway
     * by invoking the [Create Payment Transaction API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/payment_transaction_v1/create_payment_transaction).
     * The method will automatically retry creation of the payment transaction up to 5 times in case of communication issues.  
     * This is the 1st method that should be called by the payment page.
     * 
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * @example <caption>Create a new payment transaction using the Transpayrent SDK</caption>
     *  var paymentMethodId = 108;                      // VISA
     *  var body = { correlation_id : 'TP-103645',
     *               amount: { currency : 208,          // DKK
     *                         value : 100 } };
     *  sdk.createTransaction(paymentMethodId, body)
     *  .then(
     *      transaction => {
     *          // Creation of Payment Transaction failed - Display status message
     *          if (transaction.status) {
     *              // HANDLE FAILURE
     *              return Promise.reject(null);
     *          }
     *          else {
     *              // AUTHENTICATE CONSUMER BY INVOKING METHOD: authenticate
     *          }
     *      })
     *  .catch(reason => {
     *      // Low level error - Display error message
     *      if (reason) {
     *          console.error('Internal error: '+ reason);
     *      }
     *  });
     * 
     * @param {PAYMENT_METHOD_ID} paymentMethodId       The unique id of the payment method which will be used to authorize the payment for the payment transaction.
     * @param {CreatePaymentTransactionRequest} body    A representation of the consumer's payment transaction.
     * @param {Integer} attempt                         The current attempt at creating the payment transaction, defaults to 1
     * @returns {CreatePaymentTransactionResponse|Status}
     */
    Transpayrent.prototype.createTransaction = function (paymentMethodId, body, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        if (this._transactionResponseCache[paymentMethodId]) {
            return Promise.resolve(this._transactionResponseCache[paymentMethodId].body);
        }
        else {
            return this._client
                .then(
                    client => {
                        var parameters = { merchant_id : this._config.merchantId,
                                           session_id : this._config.sessionId,
                                           payment_method_id : paymentMethodId };
                        return client.apis.payment_transaction_v1.create_payment_transaction(parameters, { requestBody : body })
                            .then(
                                result => {
                                    // Google Pay or Apple Pay
                                    if (paymentMethodId == 203 || paymentMethodId == 204) {
                                        this._transactionRequestCache[paymentMethodId] = body;
                                        this._transactionResponseCache[paymentMethodId] = result;
                                    }
                                    return Promise.resolve(result);
                                },
                                // Low level error
                                reason => {
                                    if (attempt < MAX_ATTEMPTS) {
                                        return this.sleep(attempt * TIME)
                                            .then(r => this.createTransaction(paymentMethodId, body, attempt + 1) );
                                    }
                                    else {
                                        return Promise.reject(reason);
                                    }
                                });
                    },
                    // Error: Failed to load OpenAPI specification from url
                    reason => {
                        console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                        return Promise.reject(reason);
                    })
                .then(
                    result => {
                        if (result.status == 201) {
                            return result.body;
                        }
                        // API request failed
                        else {
                            return this._normalizeStatus('create_payment_transaction', result.status, result.body);
                        }
                    },
                    // Low level error
                    reason => {
                        reason.api = 'create_payment_transaction';
                        console.error(`Failed to create payment transaction for session: ${this._config.sessionId} due to: ${reason}`);
                        return Promise.reject(reason);
                    });
        }
    };

    /**
     * A representation of the payment details provided by the consumer for strong authentication of a payment transaction using 3D Secure or equivalent.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [InitializeAuthenticationRequest](/v1/model/service-bus/consumer-authentication/initialize-authentication_v1.yaml#/InitializeAuthenticationRequest)
     * @see [GooglePayPayment](/v1/model/common/payment-details/google-pay_v1.yaml#/GooglePayPayment)
     * @see [MobilePayOnlinePayment](/v1/model/common/payment-details/mobilepay-online_v1_v1.yaml#/MobilePayOnlinePayment)
     * @see [PaymentCard](/v1/model/common/payment-details/payment-card_v1.yaml#/PaymentCard)
     * @see [PaymentDetails](/v1/model/common/payment-details_v1.yaml#/PaymentDetails)
     * @see [PAYMENT_METHOD_ID](#PAYMENT_METHOD_ID)
     * 
     * @typedef {Object} InitializeAuthenticationRequest
     */
    /**
     * A representation of the details required to progress the 3D Secure process for strong consumer authentication.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [InitializeAuthenticationResponse](/v1/model/service-bus/consumer-authentication/initialize-authentication_v1.yaml#/InitializeAuthenticationResponse)
     * 
     * @typedef {Object} InitializeAuthenticationResponse
     */
    /**
     * Initializes strong consumer authentication for the specified payment transaction through the Transpayrent Payment Gateway using 3D secure or equivalent
     * by invoking the [Initialize Authentication For Transaction API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/consumer_authentication_v1/initialize_authentication_for_transaction).
     * The method will automatically retry initialization of the strong consumer authentication for the specified payment transaction up to 5 times in case of communication issues.  
     * Calling this method directly is **not** recommended, instead call method: {@link Transpayrent#authenticate} to orchestrate the complete flow for strong consumer authentication (SCA).  
     * ***Please note that API calls made using this method falls under [PCI DSS]{@link https://www.pcisecuritystandards.org/}.***
     * 
     * @see {@link Transpayrent#authenticate}
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * 
     * @param {Long} transactionId                      The unique id of the payment transaction for which strong consumer authentication using 3D secure or equivalent is required
     * @param {InitializeAuthenticationRequest} body    A representation of the payment details provided by the consumer for strong authentication of a payment transaction using 3D Secure or equivalent.
     * @param {Integer} attempt                         The current attempt at initializing strong consumer authentication for the specified payment transaction, defaults to 1
     * @returns {InitializeAuthenticationResponse|Status}
     */
    Transpayrent.prototype.initializeAuthentication = function (transactionId, body, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId,
                                       session_id : this._config.sessionId,
                                       transaction_id : transactionId };
                    return client.apis.consumer_authentication_v1.initialize_authentication_for_transaction(parameters, { requestBody : body })
                        .then(
                            result => Promise.resolve(result),
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                        .then(r => this.initializeAuthentication(transactionId, body, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    if (result.status == 200) {
                        return result.body;
                    }
                    // API request failed
                    else {
                        return this._normalizeStatus('initialize_authentication', result.status, result.body, transactionId);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'initialize_authentication';
                    reason.transaction_id = transactionId;
                    console.error(`Failed to initialize authentication for transaction: ${transactionId} due to: ${reason}`);
                    return Promise.reject(reason);
                });
    };
    
    /**
     * A representation of the entered payment details and details of the consumer's browser attributes required for strong authentication of a payment transaction using 3D Secure or equivalent.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [AuthenticateConsumerRequest](/v1/model/service-bus/consumer-authentication/consumer-authentication_v1.yaml#/AuthenticateConsumerRequest)
     * @see [Browser](/v1/model/common/browser-details/browser-details_v1.yaml#/Browser)
     * @see [GooglePayPayment](/v1/model/common/payment-details/google-pay_v1.yaml#/GooglePayPayment)
     * @see [MobilePayOnlinePayment](/v1/model/common/payment-details/mobilepay-online_v1_v1.yaml#/MobilePayOnlinePayment)
     * @see [PaymentCard](/v1/model/common/payment-details/payment-card_v1.yaml#/PaymentCard)
     * @see [PaymentDetails](/v1/model/common/payment-details_v1.yaml#/PaymentDetails)
     * @see [PAYMENT_METHOD_ID](#PAYMENT_METHOD_ID)
     * @see [Address](/v1/model/common/address/address_v1.yaml#/Address)
     * @see [ContactDetails](/v1/model/common/contact-details/contact-details_v1.yaml#/ContactDetails)
     * @see [COUNTRY](/v1/model/common/country/country_v1.yaml#/COUNTRY)
     * @see [PhoneNumber](/v1/model/common/phone-number/phone-number_v1.yaml#/PhoneNumber)
     * @see [INTERNATIONAL_DIALING_CODE](/v1/model/common/international-dialing-code_v1.yaml#/INTERNATIONAL_DIALING_CODE)
     * @see [Email](/v1/model/common/email/email_v1.yaml#/Email)
     * 
     * @typedef {Object} AuthenticateConsumerRequest
     */
    /**
     * A representation of the successful authentication of the consumer for a payment transaction using 3D Secure.
     * The payment for the payment transaction should be authorized upon receiving a successful authentication result.  
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [AuthenticationSuccessResult](/v1/model/service-bus/consumer-authentication/authentication-result_v1.yaml#/AuthenticationSuccessResult)
     * @see [AbstractAuthenticationResult](/v1/model/service-bus/consumer-authentication/authentication-result_v1.yaml#/AbstractAuthenticationResult)
     * @see [AUTHENTICATION_TYPE](/v1/model/service-bus/consumer-authentication/authentication-type_v1.yaml#/AUTHENTICATION_TYPE)
     * @see [STATUS_CODE](/v1/model/common/status-message/status-code_v1.yaml#/STATUS_CODE)
     * 
     * @typedef {Object} AuthenticationSuccessResult
     */
    /**
     * A representation of an attempted authentication of the consumer for a payment transaction,
     * which requires the consumer to complete an additional authentication challenge as part of the strong consumer authentication process using 3D Secure.  
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [AuthenticationChallengeResult](/v1/model/service-bus/consumer-authentication/authentication-result_v1.yaml#/AuthenticationChallengeResult)
     * @see [AbstractAuthenticationResult](/v1/model/service-bus/consumer-authentication/authentication-result_v1.yaml#/AbstractAuthenticationResult)
     * @see [AUTHENTICATION_TYPE](/v1/model/service-bus/consumer-authentication/authentication-type_v1.yaml#/AUTHENTICATION_TYPE)
     * @see [STATUS_CODE](/v1/model/common/status-message/status-code_v1.yaml#/STATUS_CODE)
     * 
     * @typedef {Object} AuthenticationChallengeResult
     */
    /**
     * A representation of a failed authentication of the consumer for a payment transaction using 3D Secure.
     * The consumer should be informed of the authentication failure and asked to enter the details of another card upon
     * receiving a failure authentication result or the payment transaction should be abandoned so no authorization is attempted.  
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [AuthenticationFailureResult](/v1/model/service-bus/consumer-authentication/authentication-result_v1.yaml#/AuthenticationFailureResult)
     * @see [AbstractAuthenticationResult](/v1/model/service-bus/consumer-authentication/authentication-result_v1.yaml#/AbstractAuthenticationResult)
     * @see [AUTHENTICATION_TYPE](/v1/model/service-bus/consumer-authentication/authentication-type_v1.yaml#/AUTHENTICATION_TYPE)
     * @see [STATUS_CODE](/v1/model/common/status-message/status-code_v1.yaml#/STATUS_CODE)
     * 
     * @typedef {Object} AuthenticationFailureResult
     */
    /**
     * Authenticates the consumer of the specified payment transaction through the Transpayrent Payment Gateway using 3D secure or equivalent
     * by invoking the [Authenticate Consumer API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/consumer_authentication_v1/authenticate_consumer).
     * The method will automatically retry authentication of the consumer for the specified payment transaction up to 5 times in the following scenarios:  
     *  - HTTP Status Code: 409 - Conflict: Cached Payment Transaction not yet updated with authentication data  
     *  - Low level communication error  
     * Calling this method directly is **not** recommended, instead call method: {@link Transpayrent#authenticate} to orchestrate the complete flow for strong consumer authentication (SCA).  
     * ***Please note that API calls made using this method falls under [PCI DSS]{@link https://www.pcisecuritystandards.org/}.***
     * 
     * @see {@link Transpayrent#authenticate}
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * 
     * @param {Long} transactionId                  The unique id of the payment transaction for which strong consumer authentication using 3D secure or equivalent is required
     * @param {AuthenticateConsumerRequest} body    A representation of the entered payment details and details of the consumer's browser attributes required for strong authentication of a payment transaction using 3D Secure or equivalent.
     * @param {Integer} attempt                     The current attempt at authenticating the consumer for the specified payment transaction, defaults to 1
     * @returns {AuthenticationSuccessResult|AuthenticationChallengeResult|AuthenticationFailureResult|Status}
     */
    Transpayrent.prototype.authenticateConsumer = function (transactionId, body, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId,
                                       session_id : this._config.sessionId,
                                       transaction_id : transactionId,
                                       'user-agent' : null,
                                       accept : null };
                    return client.apis.consumer_authentication_v1.authenticate_consumer_for_transaction(parameters, { requestBody : body })
                        .then(
                            result => {
                                // Cached Payment Transaction not yet updated with authentication data
                                if (result.status == 409 && attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME / 10)
                                               .then(r => this.authenticateConsumer(transactionId, body, attempt + 1) );
                                }
                                else {
                                    return Promise.resolve(result);
                                }
                            },
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                               .then(r => this.authenticateConsumer(transactionId, body, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    // Strong Consumer Authentication Success or Challenge
                    if (result.status == 200) {
                        result.body.transaction_id = transactionId;
                        return result.body;
                    }
                    // Strong Consumer Authentication Failure
                    else if (result.status == 511) {
                        result.body.transaction_id = transactionId;
                        result.body.status = result.status;
                        return result.body;
                    }
                    // // API request failed
                    else {
                        return this._normalizeStatus('authenticate_consumer', result.status, result.body, transactionId);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'authenticate_consumer';
                    reason.transaction_id = transactionId;
                    console.error(`Failed to authenticate consumer for transaction: ${transactionId} due to: ${reason}`);
                    return Promise.reject(reason);
                });
    };
    
    /**
     * Retrieves the strong consumer authentication (SCA) result for the specified payment transaction through the Transpayrent Payment Gateway using 3D secure or equivalent.
     * The method will invoke the [Get Authentication Result For Transaction API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/consumer_authentication_v1/get_authentication_result_for_transaction)
     * and automatically retry retrieving the strong consumer authentication (SCA) result for the specified payment transaction up to 5 times in the following scenarios:  
     *  - HTTP Status Code: 409 - Conflict: Cached Payment Transaction not yet updated with authentication data  
     *  - Low level communication error  
     * Please note that calling this method directly is **not** recommended, instead call method: {@link Transpayrent#authenticate} to orchestrate the complete flow for strong consumer authentication (SCA).  
     * 
     * @see {@link Transpayrent#authenticate}
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * 
     * @param {Long} transactionId  The unique id of the payment transaction for which the strong consumer authentication using 3D secure or equivalent is required should be verified
     * @param {Integer} attempt     The current attempt at retrieving the authentication result for the specified payment transaction, defaults to 1
     * @returns {AuthenticationSuccessResult|AuthenticationFailureResult|Status}
     */
    Transpayrent.prototype.getAuthenticationResult = function (transactionId, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId,
                                       session_id : this._config.sessionId,
                                       transaction_id : transactionId };
                    return client.apis.consumer_authentication_v1.get_authentication_result_for_transaction(parameters)
                        .then(
                            result => {
                                // Cached Payment Transaction not yet updated with authentication data
                                if (result.status == 409 && attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME / 10)
                                               .then(r => this.getAuthenticationResult(transactionId, body, attempt + 1) );
                                }
                                else {
                                    return Promise.resolve(result);
                                }
                            },
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                               .then(r => this.getAuthenticationResult(transactionId, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    // Strong Consumer Authentication Success
                    if (result.status == 200) {
                        result.body.transaction_id = transactionId;
                        return result.body;
                    }
                    // Strong Consumer Authentication Failure
                    else if (result.status == 511) {
                        result.body.transaction_id = transactionId;
                        result.body.status = result.status;
                        return result.body;
                    }
                    // // API request failed
                    else {
                        return this._normalizeStatus('get_authentication_result', result.status, result.body, transactionId);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'get_authentication_result';
                    reason.transaction_id = transactionId;
                    console.error(`Failed to retrieve authentication result for transaction: ${transactionId} due to: ${reason}`);
                    return Promise.reject(reason);
                });
    };

    /**
     * Convenience method for authenticating the consumer of the specified payment transaction through the Transpayrent Payment Gateway using 3D secure or equivalent.  
     * The method orchestrates each part *(initialization, authentication and verification)* of the strong consumer authentication process
     * by calling several methods internally in the SDK and automatically performs the following actions if required:
     *  * [Fingerprint the consumer's brower]{@link Transpayrent#fingerprint} in an invisible iframe
     *  * [Display an authentication challenge]{@link Transpayrent#displayChallenge} to the consumer in an iframe
     * 
     * The *look'n'feel* of the authentication challenge may be fully customized using [CSS]{@link https://en.wikipedia.org/wiki/CSS} simply by passing the appropriate iframe configuration as the 3rd argument to this method.  
     * This is the 2nd method that should be called by the payment page.  
     * Please note that:
     *  * Calling [createTransaction]{@link Transpayrent#createTransaction} should be done prior to calling this method to create a new payment transaction and obtain a transaction id
     *  * Calling this method is the recommended way to complete the strong consumer authentication (SCA) flow
     *  * The method will register [event listeners]{@link https://developer.mozilla.org/en-US/docs/Web/API/EventListener} and listen for the following events:
     *      * 3DS-authentication-complete
     *      * 3DS-fingerprint-complete
     *  * ***API calls made using this method falls under [PCI DSS]{@link https://www.pcisecuritystandards.org/}.***
     * 
     * @see {@link Transpayrent#initializeAuthentication}
     * @see {@link Transpayrent#authenticateConsumer}
     * @see {@link Transpayrent#getAuthenticationResult}
     * @see {@link Transpayrent#fingerprint}
     * @see {@link Transpayrent#displayChallenge}
     * 
     * @public
     * 
     * @example <caption>Authenticate consumer with 3D Secure or equivalent using the Transpayrent SDK</caption>
     *  var card = { payment_method_id: 108,            // VISA
     *               card_number: 4111111111111111,     // Successful Authorization: Manual Challenge with browser fingerprint
     *               expiry_month: 9,
     *               expiry_year: 22,
     *               cvv: 987,
     *               card_holder_name: 'John Doe',
     *               save: true };
     *  var address = { street : 'Arne Jacobsens Allé 7',
     *                  appartment : '5. sal',
     *                  city : 'Copenhagen S',
     *                  postal_code : '2300',
     *                  state : '82',
     *                  country: 208 };                 // Denmark
     *  var phone = { international_dialing_code: 45,   // Denmark
     *                phone_number: 12345678 };
     *  var email = 'hello@transpayrent.dk';
     *  var body = { payment_details : card,
     *               billing_address : address,
     *               shipping_address : address,
     *               contact : { mobile : phone,
     *                           work : phone,
     *                           home : phone,
     *                           email : email } };
     *  var iframeConfig = { container : document.body,
     *                       css: 'challenge',
     *                       callback : function (event, iframe) {
     *                           switch (event) {
     *                               case 'authentication-challenge-initiated':
     *                                   // DO SOMETHING BEFORE THE AUTHENTICATION CHALLENGE IS DISPLAYED
     *                                   break;
     *                               case 'authentication-challenge-completed':
     *                                   // DO SOMETHING AFTER THE AUTHENTICATION CHALLENGE IS COMPLETE
     *                                   break;
     *                           }
     *                       } };
     *  sdk.authenticate(transaction.id, body, iframeConfig);
     *  .then(
     *      authentication => {
     *          // Consumer Authentication failed
     *          if (authentication.status) {
     *              // Consumer Authentication failure
     *              if (authentication.status == 511) {
     *                  // HANDLE AUTHENTICATION FAILURE
     *                  return Promise.reject(null);
     *              }
     *              // API request failed - Display status message
     *              else {
     *                  // HANDLE COMMUNICATION ERROR
     *                  return Promise.reject(null);
     *              }
     *          }
     *          // Consumer Authentication succesfully completed
     *          else {
     *              // AUTHORIZE PAYMENT BY INVOKING METHOD: authorize
     *          }
     *      })
     *  .catch(reason => {
     *      // Low level error - Display error message
     *      if (reason) {
     *          console.error('Internal error: '+ reason);
     *      }
     *  });
     * 
     * @listens message:'3DS-fingerprint-complete'
     * @listens message:'3DS-authentication-complete'
     * 
     * @param {Long} transactionId                  The unique id of the payment transaction for which strong consumer authentication using 3D secure or equivalent is required
     * @param {AuthenticateConsumerRequest} body	A representation of the entered payment details required for strong authentication of a payment transaction using 3D Secure or equivalent.
     *                                              Please note that the `browser` property will be constructed automatically.
     * @param {IFrameConfig} config              	The configuration for the iframe which will be constructed in case the consumer needs to be presented with an authentication challenge
     * @returns {AuthenticationSuccessResult|AuthenticationFailureResult|Status}
     */
    Transpayrent.prototype.authenticate = function (transactionId, body, config) {
        body.device_channel_id = 2;	// Browser based 3D Secure authentication
        body.browser = { java_enabled : false,
                         javascript_enabled : true,
                         language : navigator.language,
                         color_depth : screen.colorDepth,
                         screen_height : screen.height,
                         screen_width : screen.width,
                         timezone_offset : new Date().getTimezoneOffset() };
        /*
         * Backwards compatibility with renamed entities in the request to the following APIs:
         *  - Initialize Authentication For Transaction
         *  - Authenticate Consumer For Transaction
         */
        if (body.card && body.hasOwnProperty('payment_details') == false) {
            console.log('Property: card is deprecated, use property: payment_details instead');
            body.payment_details = body.card;
            delete body.card;
        }
        return this.initializeAuthentication(transactionId, { payment_details : body.payment_details })
            .then(
                result => {
                    // Failure while initializing strong consumer authentication (SCA) using 3D Secure
                    if (result.status) {
                        return result;
                    }
                    else {
                        // Fingerprint browser
                        if (result.acs_url) {
                            var _handler = function (resolver, iframe, event) {
                                if (event.data && typeof event.data === 'string' && event.data.toLowerCase() == '3ds-fingerprint-complete') {
                                    iframe.remove();
                                    resolver();
                                }
                            }
                            var resolver;
                            var promise = new Promise( (resolve, reject) => {
                                resolver = resolve;
                            });
                            setTimeout( () => { iframe.remove(); resolver(); }, 10000);
                            var iframe = this.fingerprint(result.acs_url, result.request_data);
                            window.addEventListener('message', _handler.bind(null, resolver, iframe), false);
                            return promise.then(r => this.authenticateConsumer(transactionId, body),
                                                e => e);
                        }
                        else {
                            return this.authenticateConsumer(transactionId, body);
                        }
                    }
                },
                // Low level error
                reason => Promise.reject(reason) )
            .then(
                authentication => {
                    // Consumer Authentication requires challenge
                    if (authentication.result && authentication.result == "C") {
                        var _handler = function (resolver, iframe, event) {
                            if (event.data && typeof event.data === 'string' && event.data.toLowerCase() == '3ds-authentication-complete') {
                                if (config.callback) {
                                    config.callback('authentication-challenge-completed', iframe);
                                }
                                iframe.remove();
                                resolver();
                            }
                        }
                        var resolver;
                        var promise = new Promise( (resolve, reject) => {
                            resolver = resolve;
                        });
                        var iframe = this.displayChallenge(authentication.acs_url, authentication.request_data, config);
                        window.addEventListener('message', _handler.bind(null, resolver, iframe), false);
                        return promise.then(r => this.getAuthenticationResult(transactionId),
                                            e => e);
                    }
                    // Consumer Authentication complete
                    else {
                        return authentication;
                    }
                },
                // Low level error
                reason => Promise.reject(reason) );
    };

    /**
     * A representation of a payment made using a payment card such as a MasterCard credit card or a VISA debit card.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [ApplePayPayment](/v1/model/common/payment-details/apple-pay_v1.yaml#/ApplePayPayment)
     * @see [GooglePayPayment](/v1/model/common/payment-details/google-pay_v1.yaml#/GooglePayPayment)
     * @see [MobilePayOnlinePayment](/v1/model/common/payment-details/mobilepay-online_v1_v1.yaml#/MobilePayOnlinePayment)
     * @see [ConsumerWalletPayment](/v1/model/common/payment-details/consumer-wallet_v1.yaml#/ConsumerWalletPayment)
     * @see [PaymentCard](/v1/model/common/payment-details/payment-card_v1.yaml#/PaymentCard)
     * @see [PaymentDetails](/v1/model/common/payment-details_v1.yaml#/PaymentDetails)
     * @see [PAYMENT_METHOD_ID](#/PAYMENT_METHOD_ID)
     * 
     * @typedef {Object} PaymentDetails
     */
    /**
     * A representation of the details for the successful authorization of a payment transaction.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [AuthorizePaymentResponse](/v1/model/service-bus/payment-authorization/payment-authorization_v1.yaml#/AuthorizePaymentResponse)
     * @see [RecordEntityId](/v1/model/common/record-entity-id/record-entity-id_v1.yaml#/RecordEntityId)
     * @see [STATUS_CODE](/v1/model/common/status-message/status-code_v1.yaml#/STATUS_CODE)
     * 
     * @typedef {Object} AuthorizePaymentResponse
     */
    /**
     * Authorizes the payment for the specified payment transaction through the Transpayrent Payment Gateway using the provided payment details by 
     * invoking the [Authorize Payment For Transaction API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/authorize_payment_v1/authorize_payment_for_transaction).
     * The method will automatically retry authorization of the payment for the specified payment transaction up to 5 times in case of communication issues.  
     * This is the 3rd method that should be called by the payment page when authorizing a payment without storing the consumer's payment card.  
     * Please note that:
     *  * Calling [authenticate]{@link Transpayrent#authenticate} should be done prior to calling this method to authenticate the consumer and obtain a cryptogram
     *  * Call [save]{@link Transpayrent#save} prior to calling this method to securely store the consumer's payment card in Transpayren's Secure Vault and add the stored payment card to the consumer's wallet
     *  * ***API calls made using this method falls under [PCI DSS]{@link https://www.pcisecuritystandards.org/} when authorizing the payment for the payment transaction using a payment card.***
     * 
     * @see {@link Transpayrent#authenticate}
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * 
     * @example <caption>Authorize payment with payment card details using the Transpayrent SDK</caption>
     *  var card = { payment_method_id: 108,            // VISA
     *               card_number: 4111111111111111,     // Successful Authorization: Manual Challenge with browser fingerprint
     *               expiry_month: 9,
     *               expiry_year: 22,
     *               cvv: 987,
     *               card_holder_name: 'John Doe',
     *               save: true,
     *               cryptogram : [CRYPTOGRAM RETURNED BY STRONG CONSUMER AUTHENTICATION] };
     *  var body = { payment_details : card }
     *  sdk.authorize(authentication.transaction_id, body)
     *  .then(
     *      authorization => {
     *          // Payment Authorization failed - Display status message
     *          if (authorization.status) {
     *              // HANDLE AUTHORIZATION FAILURE
     *          }
     *          // Payment Authorization completed - Display success message
     *          else {
     *              // HANDLE AUTHORIZATION SUCCESS
     *          }
     *      })
     *  .catch(reason => {
     *      // Low level error - Display error message
     *      if (reason) {
     *          console.error('Internal error: '+ reason);
     *      }
     *  });
     * 
     * @example <caption>Authorize payment with stored payment card using the Transpayrent SDK</caption>
     *  var request = { payment_method_id: 201,            // Consumer Wallet
     *                  valuable_id: [VALUABLE ID RETURNED BY SDK METHOD: save] };
     *  sdk.authorize(save.transaction_id, request)
     *  .then(
     *      authorization => {
     *          // Payment Authorization failed - Display status message
     *          if (authorization.status) {
     *              // HANDLE AUTHORIZATION FAILURE
     *          }
     *          // Payment Authorization completed - Display success message
     *          else {
     *              // HANDLE AUTHORIZATION SUCCESS
     *          }
     *      })
     *  .catch(reason => {
     *      // Low level error - Display error message
     *      if (reason) {
     *          console.error('Internal error: '+ reason);
     *      }
     *  });
     *  
     * @param {Long} transactionId              The unique id of the payment transaction for which the payment should be authorized
     * @param {PaymentDetails} paymentDetails   A representation of a payment details (card details, wallet details, voucher details etc.), which will be used to authorize the payment for the payment transaction
     * @param {Integer} attempt                 The current attempt at authorizing the payment for the specified payment transaction, defaults to 1
     * @returns {AuthorizePaymentResponse|Status}
     */
    Transpayrent.prototype.authorize = function (transactionId, paymentDetails, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        var body = null;
        if (paymentDetails.hasOwnProperty('payment_details') ) {
            body = paymentDetails;
        }
        else {
            console.log('Passing payment details directly is deprecated, pass them using property: payment_details instead');
            body = { payment_details : paymentDetails };
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId,
                                       session_id : this._config.sessionId,
                                       transaction_id : transactionId };
                    return client.apis.authorize_payment_v1.authorize_payment_for_transaction(parameters, { requestBody : body })
                        .then(
                            result => Promise.resolve(result),
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                        .then(r => this.authorize(transactionId, card, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    // Payment Successfully Authorized or Payment Already Authorized
                    if (result.status == 200) {
                        // Clear cache
                        delete this._initializationCache[transactionId];
                        delete this._tokenCache[body.payment_details.payment_method_id];
                        delete this._transactionRequestCache[body.payment_details.payment_method_id];
                        delete this._transactionResponseCache[body.payment_details.payment_method_id];

                        return result.body;
                    }
                    // Payment Authorization In Progress
                    else if (result.status == 202) {
                        const _method = (function (transactionId, attempt, max) {
                            return this.getTransaction(transactionId)
                                       .then(transaction => {
                                            if (transaction.authorized_amount.value > 0) {
                                                // Clear cache
                                                delete this._initializationCache[transactionId];
                                                delete this._tokenCache[body.payment_details.payment_method_id];
                                                delete this._transactionRequestCache[body.payment_details.payment_method_id];
                                                delete this._transactionResponseCache[body.payment_details.payment_method_id];
                        
                                                result.body.payment_instrument_id = transaction.payment_instrument_id;
                                                result.body.status_code = 620021;
                                                return Promise.resolve(result.body);
                                            }
                                            else if (attempt < max) {
                                                return this.sleep(attempt * TIME)
                                                           .then(r => _method(transaction.id, attempt+1, max) ); 
                                            }
                                            else {
                                                var reason = { api : 'authorize_payment',
                                                               status : result.status,
                                                               transaction_id : transactionId,
                                                               messages : [ { code : result.body.status_code, message : 'Payment authorization in progress' } ] };
                                                return Promise.reject(reason);
                                            }
                                       })
                                       .catch(reason => {
                                            // Low level error - Display error message
                                            if (reason) {
                                                console.error(`Internal error: ${reason} ${reason.stack}`);
                                            }
                                            if (attempt < max) {
                                                return this.sleep(attempt * TIME)
                                                           .then(r => _method(transaction.id, attempt+1, max) ); 
                                            }
                                       });
                        }).bind(this);
                        return _method(transactionId, 1, MAX_ATTEMPTS);
                    }
                    // API request failed
                    else {
                        return this._normalizeStatus('authorize_payment', result.status, result.body, transactionId);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'authorize_payment';
                    reason.transaction_id = transactionId;
                    console.error(`Failed to authorize the payment for transaction: ${transactionId} due to: ${reason}`);
                    return Promise.reject(reason);
                });
    };

    /**
     * A representation of the payment details for the payment instrument, such as a MasterCard credit card or a VISA debit card, which will be saved.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [SaveConsumerValuableRequest](/v1/model/service-bus/consumer-valuable-storage/consumer-valuable-storage_v1.yaml#/SaveConsumerValuableRequest)
     * @see [ApplePayPayment](/v1/model/common/payment-details/apple-pay_v1.yaml#/ApplePayPayment)
     * @see [GooglePayPayment](/v1/model/common/payment-details/google-pay_v1.yaml#/GooglePayPayment)
     * @see [MobilePayOnlinePayment](/v1/model/common/payment-details/mobilepay-online_v1_v1.yaml#/MobilePayOnlinePayment)
     * @see [PaymentCard](/v1/model/common/payment-details/payment-card_v1.yaml#/PaymentCard)
     * @see [PaymentDetails](/v1/model/common/payment-details_v1.yaml#/PaymentDetails)
     * @see [PAYMENT_METHOD_ID](#/PAYMENT_METHOD_ID)
     * 
     * @typedef {Object} SaveConsumerValuableRequest
     */
    /**
     * A representation of the details for successfully saving the provided payment card.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [SaveConsumerValuableResponse](/v1/model/service-bus/consumer-valuable-storage/consumer-valuable-storage_v1.yaml#/SaveConsumerValuableResponse)
     * @see [RecordEntityId](/v1/model/common/record-entity-id/record-entity-id_v1.yaml#/RecordEntityId)
     * @see [STATUS_CODE](/v1/model/common/status-message/status-code_v1.yaml#/STATUS_CODE)
     * 
     * @typedef {Object} SaveConsumerValuableResponse
     */
    /**
     * Securely stores the provided payment instrument for the specified payment transaction into Transpayrent's Secure Vault
     * by invoking the [Save Consumer Valuable For Transaction API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/consumer_wallet_v1/save_consumer_valuable_for_transaction).
     * The method will automatically retry securely storing the provided payment instrument for the specified payment transaction up to 5 times in case of communication issues.  
     * The saved payment instrument is automatically added to the consumer's wallet if a consumerId is provided in the request or was specified for the Payment Session.  
     * This is the 3rd method that should be called by the payment page when storing the consumer's payment card.  
     * Please note that:
     *  * Calling [authenticate]{@link Transpayrent#authenticate} should be done prior to calling this method to authenticate the consumer and obtain a cryptogram
     *  * Merchants can only save a payment instrument for a payment transaction that is part of a payment session owned by the merchant
     *  * ***API calls made using this method falls under [PCI DSS]{@link https://www.pcisecuritystandards.org/} when saving a payment card.***
     * 
     * @see {@link Transpayrent#authenticate}
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * 
     * @example <caption>Save payment instrument using the Transpayrent SDK</caption>
     *  var card = { payment_method_id: 108,            // VISA
     *               card_number: 4111111111111111,     // Successful Authorization: Manual Challenge with browser fingerprint
     *               expiry_month: 9,
     *               expiry_year: 22,
     *               cvv: 987,
     *               card_holder_name: 'John Doe',
     *               cryptogram : [CRYPTOGRAM RETURNED BY STRONG CONSUMER AUTHENTICATION] };
     *  var body = { payment_details : card,
     *               consumer_id : "CID-12345",         // Defaults to the Consumer ID from the Payment Session if omitted
     *               name : "My VISA Card" }
     *  sdk.save(authentication.transaction_id, body)
     *  .then(
     *      save => {
     *          // Saving payment instrument failed - Display status message
     *          if (save.status) {
     *              // HANDLE SAVE FAILURE
     *          }
     *          // Payment instrument successfully saved
     *          else {
     *              // AUTHORIZE PAYMENT BY INVOKING METHOD: authorize
     *          }
     *      })
     *  .catch(reason => {
     *      // Low level error - Display error message
     *      if (reason) {
     *          console.error('Internal error: '+ reason);
     *      }
     *  });
     *  
     * @param {Long} transactionId                  The unique id of the payment transaction for which the payment card was authenicated
     * @param {SaveConsumerValuableRequest} body    The payment details for the payment card which will be securely stored in Transpayrent's Secure Valut and added to the consumer's wallet
     * @param {Integer} attempt                     The current attempt at securely storing the provided payment instrument for the specified payment transaction, defaults to 1
     * @returns {SaveConsumerValuableResponse|Status}
     */
     Transpayrent.prototype.save = function (transactionId, body, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        // Backwards compatibility with renamed entity in the request to the "Save Consumer Valuable For Transaction" API
        if (body.card && body.hasOwnProperty('payment_details') == false) {
            console.log('Property: card is deprecated, use property: payment_details instead');
            body.payment_details = body.card;
            delete body.card;
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId,
                                       session_id : this._config.sessionId,
                                       transaction_id : transactionId };
                    return client.apis.consumer_wallet_v1.save_consumer_valuable_for_transaction(parameters, { requestBody : body })
                        .then(
                            result => Promise.resolve(result),
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                        .then(r => this.save(transactionId, body, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    if (result.status == 200) {
                        // Clear cache
                        delete this._initializationCache[transactionId];
                        delete this._tokenCache[body.payment_details.payment_method_id];
                        delete this._transactionRequestCache[body.payment_details.payment_method_id];
                        delete this._transactionResponseCache[body.payment_details.payment_method_id];

                        result.body.transaction_id = transactionId;
                        return result.body;
                    }
                    // API request failed
                    else {
                        return this._normalizeStatus('save_consumer_valuable', result.status, result.body, transactionId);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'save_consumer_valuable';
                    reason.transaction_id = transactionId;
                    console.error(`Failed to save payment card for transaction: ${transactionId} due to: ${reason}`);
                    return Promise.reject(reason);
                });
    };

    /**
     * A representation of the initialization details provided by the consumer for initializing the payment for a payment transaction through an upstream 3rd party provider such as Apple Pay, Google Pay or MobilePay Online.  
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [InitializePaymentRequest](/v1/model/service-bus/payment-initialization/payment-initialization_v1.yaml#/InitializePaymentRequest)
     * @see [InitializationDetails](/v1/model/service-bus/payment-initialization/initialization-details_v1#/InitializationDetails)
     * @see [MobilePayOnlineInitialization](/v1/model/service-bus/payment-initialization/mobilepay-online_v1.yaml#/MobilePayOnlineInitialization)
     * @see [PAYMENT_METHOD_ID](#PAYMENT_METHOD_ID)
     * 
     * @typedef {Object} InitializePaymentRequest
     */
    /**
     * A representation of the details required to authorize the payment for a payment transaction using an upstream 3rd party provider such as Apple Pay, Google Pay or MobilePay Online.  
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [InitializePaymentResponse](/v1/model/service-bus/payment-initialization/payment-initialization_v1.yaml#/InitializePaymentResponse)
     * @see [PaymentConnection](/v1/model/service-bus/payment-initialization/payment-connection_v1.yaml#/PaymentConnection)
     * 
     * @typedef {Object} InitializePaymentResponse
     */
    /**
     * Initializes the payment for the specified payment transaction with the upstream 3rd party provider through the Transpayrent Payment Gateway
     * by invoking the [Initialize Payment For Transaction API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/initialize_payment_v1/initialize_payment_for_transaction).  
     * The method will automatically retry initialization of the payment for the specified payment transaction up to 5 times in case of communication issues.  
     * Calling this method directly is **not** recommended, instead call method: {@link Transpayrent#initialize} to orchestrate the complete flow for initializing a payment for the payment transaction.  
     * 
     * @see {@link Transpayrent#initialize}
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * 
     * @param {Long} transactionId               The unique id of the payment transaction for which the payment will be initialized with a 3rd party provider such as Apple Pay, Google Pay or MobilePay Online
     * @param {InitializePaymentRequest} body    A representation of the initialization details provided by the consumer for the selected payment method
     * @param {Integer} attempt                  The current attempt at initializing the payment for the specified payment transaction, defaults to 1
     * @returns {InitializePaymentResponse|Status}
     */
     Transpayrent.prototype.initializePayment = function (transactionId, body, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId,
                                       session_id : this._config.sessionId,
                                       transaction_id : transactionId };
                    return client.apis.initialize_payment_v1.initialize_payment_for_transaction(parameters, { requestBody : body })
                        .then(
                            result => Promise.resolve(result),
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                        .then(r => this.initializePayment(transactionId, body, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    if (result.status == 200) {
                        return result.body;
                    }
                    // API request failed
                    else {
                        return this._normalizeStatus('initialize_payment', result.status, result.body, transactionId);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'initialize_payment';
                    reason.transaction_id = transactionId;
                    console.error(`Failed to initialize payment for transaction: ${transactionId} due to: ${reason}`);
                    return Promise.reject(reason);
                });
    };
    /**
     * A representation of the initialization details provided by the consumer for initializing the payment for a payment transaction through an upstream 3rd party provider such as Apple Pay, Google Pay or MobilePay Online.  
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [InitializationDetails](/v1/model/service-bus/payment-initialization/initialization-details_v1#/InitializationDetails)
     * @see [MobilePayOnlineInitialization](/v1/model/service-bus/payment-initialization/mobilepay-online_v1.yaml#/MobilePayOnlineInitialization)
     * @see [PAYMENT_METHOD_ID](#PAYMENT_METHOD_ID)
     * 
     * @typedef {Object} InitializationDetails
     */
    /**
     * Initializes the payment for the specified payment transaction with the upstream 3rd party provider through the Transpayrent Payment Gateway
     * by invoking the [Initialize Payment For Transaction API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/initialize_payment_v1/initialize_payment_for_transaction).  
     * The method will automatically retry initialization of the payment for the specified payment transaction up to 5 times in case of communication issues.  
     * This is the 2nd method that should be called by the payment page when authorizing a payment using a 3rd party provider such as Apple Pay, Google Pay or MobilePay Online.  
     * Please note that:
     *  * Calling [createTransaction]{@link Transpayrent#createTransaction} should be done prior to calling this method to create a new payment transaction and obtain a transaction id
     *  * Calling this method is the recommended way to complete the flow for initializing the payment for a payment transaction using a 3rd party provider such as Apple Pay, Google Pay or MobilePay Online.  
     *  * The method will register [event listeners]{@link https://developer.mozilla.org/en-US/docs/Web/API/EventListener} and listen for the following events:
     *      * payment-initialization-complete
     *  * 3rd party wallets, such as Apple Pay and Google Pay, that rely on the [Payment Request API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API}
     *    prohibits the payment sheet from being displayed asynchronously, which requires this method to be called from within an [async function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function}
     *    using the [await operator]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await}.
     * 
     * @see {@link Transpayrent#initializePayment}
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * 
     * @example <caption>Initialize payment with MobilePay Online using the Transpayrent SDK</caption>
     *  var body = { payment_method_id: 202,            // MobilePay Online
     *               mobile: { international_dialing_code: 45,
     *                         phone_number: 89671108 }
     *               save: false };
     *  var iframeConfig = { container : document.body,
     *                       css: 'initialization',
     *                       callback : function (event, iframe) {
     *                           switch (event) {
     *                              case 'payment-initialization-initiated':
     *                                  // DO SOMETHING BEFORE THE INITIALIZATION OF THE PAYMENT FOR THE PAYMENT TRANSACTION IS DISPLAYED
     *                                  break;
     *                              case 'payment-initialization-completed':
     *                                  // DO SOMETHING AFTER INITIALIZATION OF THE PAYMENT FOR THE PAYMENT TRANSACTION IS COMPLETE
     *                                  break;
     *                           }
     *                       } };
     *  sdk.initialize(transaction.id, body, iframeConfig)
     *  .then(
     *      initialization => {
     *          // Payment Initialization failed - Display status message
     *          if (initialization.status) {
     *              // HANDLE INITIALIZATION FAILURE
     *          }
     *          // Payment Initialization completed - Authorize payment
     *          else {
     *              // AUTHORIZE PAYMENT BY INVOKING METHOD: authorize
     *          }
     *      })
     *  .catch(reason => {
     *      // Low level error - Display error message
     *      if (reason) {
     *          console.error('Internal error: '+ reason);
     *      }
     *  });
     *  
     * @param {Long} transactionId          The unique id of the payment transaction for which the payment will be initialized
     * @param {InitializationDetails} body  A representation of the initialization details provided by the consumer the selected payment method
     * @param {IFrameConfig} config         The configuration for the iframe which will be constructed in case the consumer needs to complete initialization of the payment for the payment transaction using a [lightbox overlay]{@link https://en.wikipedia.org/wiki/Lightbox_(JavaScript)}
     * @returns {InitializePaymentResponse|Status}
     */
     Transpayrent.prototype.initialize = function (transactionId, body, config) {
        if (body.hasOwnProperty('device_channel_id') == false) {
            body.device_channel_id = 2;	// Browser based payment initialization
        }
        if (this._initializationCache[transactionId]) {
            const result = Object.assign(this._initializationCache[transactionId],
                                         { token : this._tokenCache[body.payment_method_id] });
            return Promise.resolve(result);
        }
        else {
            return this.initializePayment(transactionId, { initialization_details : body })
                .then(
                    result => {
                        // Failure while initializing the payment of the payment transaction with the upstream 3rd party provider
                        if (result.status) {
                            return result;
                        }
                        else {
                            // 
                            if (result.connection) {
                                // Lightbox for 3rd party wallets such as MobilePay Online
                                if (result.connection.action == 3) {
                                    var _handler = function (resolver, lightbox, event) {
                                        if (event.data && typeof event.data === 'string' && (event.data.toLowerCase() == 'payment-initialization-completed' || event.data.toLowerCase() == 'payment-initialization-expired') ) {
                                            if (config.callback) {
                                                config.callback(event.data.toLowerCase(), lightbox);
                                            }
                                            lightbox.remove();
                                            resolver();
                                        }
                                        // MobilePay Online
                                        else if (event.data && typeof event.data === 'string' && event.data.startsWith("mobilepay:") ) {
                                            if (config.callback) {
                                                config.callback('payment-initialization-completed', lightbox);
                                            }
                                            var codes = { 0 : 620021,   // Completed
                                                          1 : 640000,   // Rejected
                                                          3 : 640135,   // Expired
                                                          4 : 620027    // Cancelled
                                                        };
                                            var params = new URLSearchParams(event.data.substring(10) );
                                            lightbox.remove();
                                            // Completed
                                            if (parseInt(params.get("rc") ) == 0) {
                                                resolver();
                                            }
                                            else {
                                                var reason = { api : 'initialize_payment',
                                                               status : 502,
                                                               transaction_id : transactionId,
                                                               messages : [ { code : codes[parseInt(params.get("rc") )], message : params.get("message") } ] };
                                                rejecter(reason);
                                            }
                                        }
                                    }
                                    var resolver;
                                    var rejecter;
                                    var promise = new Promise( (resolve, reject) => {
                                        resolver = resolve;
                                        rejecter = reject;
                                    });
                                    var lightbox = this.displayPaymentInitialization(body.payment_method_id, result.connection.url, result.connection.method, result.connection.data, config);
                                    window.addEventListener('message', _handler.bind(null, resolver, lightbox), false);
                                    // Lightbox constructed using the data uri scheme (data:)
                                    if (result.connection.method == 4) {
                                        const _method = (function (transactionId, attempt, max) {
                                            this.getTransaction(transactionId)
                                            .then(transaction => {
                                                if (transaction.authorized_amount.value > 0) {
                                                    window.postMessage('payment-initialization-completed', '*');
                                                }
                                                else if (attempt < max) {
                                                    setTimeout(_method, 10000, transaction.id, attempt+1, max);   
                                                }
                                            })
                                            .catch(reason => {
                                                // Low level error - Display error message
                                                if (reason) {
                                                    console.error(`Internal error: ${reason} ${reason.stack}`);
                                                }
                                                if (attempt < max) {
                                                    setTimeout(_method, 10000, transaction.id, attempt+1, max);   
                                                }
                                            });
                                        }).bind(this);
                                        setTimeout(_method, 10000, transactionId, 0, parseInt( (new Date(result.connection.data.expiry) - Date.now() ) / 1000) );
                                    }
                                    return promise.then(r => result,
                                                        e => e);
                                }
                                // Helper SDK for 3rd party wallets such as Apple Pay or Google Pay
                                else if (result.connection.action == 4) {
                                    if (config) {
                                        result.callback = config.callback;
                                    }
                                    result.token = this._tokenCache[body.payment_method_id];
                                    this._initializationCache[transactionId] = result;
                                    return result;
                                }
                                // Redirect or App-Switch
                                else {
                                    result.save = body.save;
                                    return result;
                                }
                            }
                            else {
                                result.save = body.save;
                                return result;
                            }
                        }
                    },
                    // Low level error
                    reason => Promise.reject(reason) );
        }
    };

    /**
     * Retrieves the specified payment transaction from the Transpayrent Payment Gateway.
     * The method will invoke the [Get Transaction API](/webjars/swagger-ui/index.html?configUrl=/v3/api-docs/swagger-config#/payment_transaction_v1/get_transaction)
     * and automatically retry retrieving the specified payment transaction up to 5 times in case of communication issues.  
     * 
     * @see {@link Transpayrent#sleep}
     * 
     * @public
     * 
     * @param {Long} transactionId  The unique id of the payment transaction that will be retrieved
     * @param {Integer} attempt     The current attempt at retrieving the specified payment transaction, defaults to 1
     * @returns {PaymentTransaction|Status}
     */
    Transpayrent.prototype.getTransaction = function (transactionId, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId,
                                       session_id : this._config.sessionId,
                                       transaction_id : transactionId };
                    return client.apis.payment_transaction_v1.get_payment_transaction(parameters)
                        .then(
                            result => Promise.resolve(result),
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                        .then(r => this.getTransaction(transactionId, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    // Payment Transaction retrieved
                    if (result.status == 200) {
                        return result.body;
                    }
                    // // API request failed
                    else {
                        return this._normalizeStatus('get_transaction', result.status, result.body, transactionId);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'get_transaction';
                    reason.transaction_id = transactionId;
                    console.error(`Failed to retrieve transaction: ${transactionId} due to: ${reason}`);
                    return Promise.reject(reason);
                });
    };

    /**
     * Determines the payment method based on the specified {@code cardNumber}.
     * The method may be used to dynamically route the payment transaction to an upstream provider based on the card details entered by the consumer.
     * 
     * @see <a href="https://en.wikipedia.org/wiki/Payment_card_number">Wikipedia - Payment card number</a>
     * @see <a href="https://www.discoverglobalnetwork.com/content/dam/discover/en_us/dgn/docs/IPP-VAR-Enabler-Compliance.pdf">Discover - IIN Range</a>
     * @see <a href="https://baymard.com/checkout-usability/credit-card-patterns">Credit Card IIN Ranges</a>
     * 
     * @public
     * 
     * @param {Long} cardNumber     The card number which should be used to determine the payment method
     * @return {PAYMENT_METHOD_ID}  The payment method that was determined based on the specified {@code cardNumber} or -1 if the payment method could not be determined
     */
    Transpayrent.prototype.getPaymentMethodId = function (cardNumber) {
        cardNumber = cardNumber.toString().replace(/[^0-9]/g, '');
        if (cardNumber.toString().length >= 6) {
            var prefix = cardNumber.toString().substring(0, 6);
            // Dankort
            if (501900 <= prefix && prefix <= 501999) {
                return 102;
            }
            // VISA Dankort
            else if (457100 <= prefix && prefix <= 457199) {
                return 103;
            }
            // American Express
            else if ( (340000 <= prefix && prefix <= 349999) || (370000 <= prefix && prefix <= 379999) ) {
                return 101;
            }
            // Diners Club
            else if (360000 <= prefix && prefix <= 369999) {
                return 104;
            }
            // JCB
            else if (352800 <= prefix && prefix <= 358999) {
                return 106;
            }
            // VISA Electron
            else if (prefix == 400102 || (402600 <= prefix && prefix <= 402699) || prefix == 417500
                || (491300 <= prefix && prefix <= 491399) || (491730 <= prefix && prefix <= 491739) ) {

                return 110;
            }
            // VISA
            else if (400000 <= prefix && prefix <= 499999) {
                return 109;
            }
            // ELO
            else if (prefix == 504175 || prefix == 506707 || prefix == 506708 || prefix == 506715
                || (506717 <= prefix && prefix <= 506722) || (506724 <= prefix && prefix <= 506736)
                || (506739 <= prefix && prefix <= 506743) || (506745 <= prefix && prefix <= 506747)
                || (506750 <= prefix && prefix <= 506753) || (506774 <= prefix && prefix <= 506778)
                || (509000 <= prefix && prefix <= 509007) || prefix == 509009
                || (509013 <= prefix && prefix <= 509030) || prefix == 509034 || prefix == 509035
                || (509038 <= prefix && prefix <= 509042) || (509044 <= prefix && prefix <= 509089)
                || (509091 <= prefix && prefix <= 509100) || (509103 <= prefix && prefix <= 509109)
                || (509146 <= prefix && prefix <= 509149) || (509151 <= prefix && prefix <= 509158)
                || (509183 <= prefix && prefix <= 509189) || (509220 <= prefix && prefix <= 509222)
                || (509257 <= prefix && prefix <= 509288) || prefix == 509357
                || (509407 <= prefix && prefix <= 509422) || prefix == 509431
                || (509457 <= prefix && prefix <= 509465) || prefix == 509550 || prefix == 509551 || prefix == 509636
                || (509722 <= prefix && prefix <= 509729) || (509765 <= prefix && prefix <= 509769)
                || (509987 <= prefix && prefix <= 509991) || prefix == 601157 || prefix == 601158 || prefix == 627780 || prefix == 636368
                || (650031 <= prefix && prefix <= 650033) || (650035 <= prefix && prefix <= 650051)
                || (650406 <= prefix && prefix <= 650414) || (650422 <= prefix && prefix <= 650429)
                || (650434 <= prefix && prefix <= 650439) || (650485 <= prefix && prefix <= 650538)
                || (650542 <= prefix && prefix <= 650544) || (650552 <= prefix && prefix <= 650566)
                || (650577 <= prefix && prefix <= 650598) || (650720 <= prefix && prefix <= 650727)
                || (650901 <= prefix && prefix <= 650923) || prefix == 650928 || prefix == 650938 || prefix == 650939
                || (650946 <= prefix && prefix <= 650951) || prefix == 650954 || prefix == 650955
                || (650959 <= prefix && prefix <= 650968) || (650971 <= prefix && prefix <= 650973)
                || (651652 <= prefix && prefix <= 651671) || (651674 <= prefix && prefix <= 651679)
                || (655000 <= prefix && prefix <= 655019) || (655021 <= prefix && prefix <= 655036)
                || (655051 <= prefix && prefix <= 655057) || prefix == 636297 || prefix == 506699) {
                
                return 111;
            }
            // Discover
            else if ( (601100 <= prefix && prefix <= 601199 ) || (644000 <= prefix && prefix <= 649999)
                || (650000 <= prefix && prefix <= 659999) || (622126 <= prefix && prefix <= 622925) ) {

                return 105;
            }
            // Hipercard
            else if (prefix == 606282 || prefix == 637095 || prefix == 637568 || prefix == 637599 || prefix == 637609 || prefix == 637612) {
                return 112;
            }
            // MasterCard
            else if (222100 <= prefix && prefix <= 272099 || (510000 <= prefix && prefix <= 559999) ) {
                return 108;
            }
            // Maestro
            else if ( (500000 <= prefix && prefix <= 509999) || (560000 <= prefix && prefix <= 589999)
                    || (600000 <= prefix && prefix <= 699999) ) {

                return 107;
            }
            // Other unsupported card type
            else {
                return -1;
            }
        }
        // Not enough data available to determine card type
        else {
            return -1;
        }
    }
    /**
     * Validates that the specified card number is valid using the following rules:
     *  - Contains only numbers
     *  - Does not start with a 0 
     *  - Is 13 to 19 digits
     *  - Is compliant with the [Luhn algorithm]{@link https://en.wikipedia.org/wiki/Luhn_algorithm}
     * 
     * @see [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm)
     * 
     * @public
     * 
     * @param {Long} cardNumber The card number which should be validated
     * @returns {Boolean}       True if the specified card number is valid otherwise false
     */
    Transpayrent.prototype.isCardNumberValid = function (cardNumber) {
        var pattern = /^[1-9][0-9]{12,18}$/;
        // Input is valid
        if (pattern.test(cardNumber) ) {
            var checkDigit = 0;
            var isEven = false;

            for (var i=cardNumber.length-1; i>=0; i--) {
                var currentDigit = parseInt(cardNumber.charAt(i), 10);

                if (isEven && (currentDigit *= 2) > 9) {
                    currentDigit -= 9;
                }

                checkDigit += currentDigit;
                isEven = !isEven;
            }

            return (checkDigit % 10) == 0;
        }
        else {
            return false;
        }
    }
    /**
     * Validates that the specified expiry date is valid.
     * That is the specified expiry date is not in the past.
     * 
     * @public
     * 
     * @param {Integer} expiryMonth The payment card's expiry month expressed as 2-digits, i.e. 01 for January
     * @param {Integer} expiryYear  The payment card's expiry year expressed as 2-digits, i.e. 23 for 2023
     * @returns {Boolean}           True if the specified expiry date is valid otherwise false
     */
    Transpayrent.prototype.isExpiryDateValid = function (expiryMonth, expiryYear) {
        var date = new Date();
        // Input is valid
        if (/^[0-9]{1,2}$/.test(expiryMonth) && 1 <= expiryMonth && expiryMonth <= 12
            && /^[2-9][0-9]{1,3}$/.test(expiryYear) ) {
            
            // Expiry year provided as 2 digits
            if (expiryYear < 100) {
                expiryYear = parseInt(expiryYear) + 2000;
            }
            // Expiry date is not in the past
            if ( (expiryMonth >= date.getUTCMonth() + 1 && expiryYear == date.getUTCFullYear() )
                || expiryYear > date.getUTCFullYear() ) {
                
                return true;
            }
            else {
                return false;
            }
        }
        else {
            return false;
        }
    }
    /**
     * Validates that the specified Card Verification Value (CVV) is valid for the specified card type:
     *  - CVV must be greater than 0 and less than 10000 for American Express
     *  - CVV must be greater than 0 and less than 1000 for other card types
     * 
     * Please note that the validation logic relies on the Transpayrent Payment Gateway automatically prefixing the CVV with 0's
     * to provide the correct number of digits for the card type:
     *  - 4-digits for American Express
     *  - 3-digits for other card types
     * 
     * @public
     * 
     * @param {PAYMENT_METHOD_ID} paymentMethodId   The payment method for the payment card for which the CVV should be validated
     * @param {Integer|String} cvv                  The payment card's Card Verification Value (CVV)
     * @returns {Boolean}                           True if the specified Card Verification Value (CVV) is valid otherwise false
     */
    Transpayrent.prototype.isCVVValid = function (paymentMethodId, cvv) {
        // Payment Card
        if (100 < paymentMethodId && paymentMethodId < 200) {
            cvv = parseInt(cvv);
            var max = 1000;
            // American Express
            if (paymentMethodId == 101) {
                max = 10000;
            }
            
            return 0 <= cvv && cvv < max;
        }
        else {
            return false;
        }
    }
    /**
     * Determines whether the completion of the payment using the specified payment method is exempt from Strong Consumer Authentication (SCA).
     * 
     * @public
     * 
     * @param {PAYMENT_METHOD_ID} paymentMethodId   The payment method selected by the consumer
     * @param {Amount} amount                       The payment transaction's amount that will be authorized
     * @param {Boolean} save                        Flag indicating whether the consumer has elected to securely store the provided details for the payment method upon successful authorization
     * @returns {Boolean}                           True if payment is exempt from Strong Consumer Authentication (SCA) otherwise false.
     */
    Transpayrent.prototype.isSCAExempt = function (paymentMethodId, amount, save) {
        // Payment Card or Google Pay
        if ( (100 < paymentMethodId && paymentMethodId < 200) || paymentMethodId == 203) {
            
            if (save || SCA_AMOUNTS[amount.currency] < amount.value) {
                return false;
            }
            else {
                return true;
            }
        }
        // MobilePay Online: Strong Consumer Authentication will be handled within the MobilePay App if required
        // Apple Pay: Strong Consumer Authentication has already been handled by Apple
        else if (paymentMethodId == 202 || paymentMethodId == 204) {
            return true;
        }
        else {
            return save ? false : true;
        }
    }
    /**
     * Retrieves the status message for the specified status code from the internal cache.
     * 
     * @see [STATUS_CODE](/v1/model/common/status-message/status-code_v1.yaml#/STATUS_CODE)
     * 
     * @public
     * 
     * @param {Integer} statusCode  The status code for which the status message will be returned
     * @returns {String}            The status messages for the specified status code
     */
    Transpayrent.prototype.getStatusMessage = function (statusCode) {
        return this._statuses[statusCode];
    }
    /**
     * Localizes the message for the specified status code using the provided language.
     * The message for the specified status code will be localized using the browser's Accept-Language header if a language isn't specified.
     * 
     * @see [STATUS_CODE](/v1/model/common/status-message/status-code_v1.yaml#/STATUS_CODE)
     * @see [LANGUAGE](/v1/model/text-translator/language/language_v1.yaml#/LANGUAGE)
     * 
     * @public
     * 
     * @param {Integer} statusCode  The status code for which the message will be localized
     * @param {String} language     The <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">ISO-639-1 code</a> for the language the message for the specified status code should be translated to
     * @param {Integer} attempt     The current attempt at localizing the message for the specified status code, defaults to 1
     * @returns {String}            The localized message for the specified status code
     */
    Transpayrent.prototype.getLocalizedStatusMessage = function (statusCode, language, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId };
                    if (language && language.length == 2) {
                        parameters.language = language;
                    }
                    else {
                        parameters['accept-language'] = null;
                    }
                    var promise = null;
                    parameters.status_code = statusCode;
                    if (parameters.language) {
                        promise = client.apis.localize_message_v1.localize_message_for_status_code_using_language(parameters);
                    }
                    else {
                        promise = client.apis.localize_message_v1.localize_message_for_status_code(parameters);
                    }
                    return promise
                        .then(
                            result => Promise.resolve(result),
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                        .then(r => this.getLocalizedStatusMessage(statusCode, language, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    if (result.status == 200) {
                        return result.body.text;
                    }
                    else if (result.status == 404) {
                        return this._statuses[statusCode];
                    }
                    // API request failed
                    else {
                        return this._normalizeStatus('localize_message', result.status, result.body);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'localize_message';
                    console.error(`Failed to retrieved localized message for status code: ${statusCode} due to: ${reason}`);
                    return Promise.reject(reason);
                });
    }
    /**
     * Localizes the specified message using the provided language.
     * The specified message will be localized using the browser's Accept-Language header if a language isn't specified.
     * 
     * @see [LANGUAGE](/v1/model/text-translator/language/language_v1.yaml#/LANGUAGE)
     * 
     * @public
     * 
     * @param {String} message      The message that will be localized
     * @param {String} language     The <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">ISO-639-1 code</a> for the language the message should be translated to
     * @param {Integer} attempt     The current attempt at localizing the specified message, defaults to 1
     * @returns {String}            The localized message
     */
    Transpayrent.prototype.getLocalizedMessage = function (message, language, attempt) {
        if (attempt == null) {
            attempt = 1;
        }
        return this._client
            .then(
                client => {
                    var parameters = { merchant_id : this._config.merchantId };
                    if (language && language.length == 2) {
                        parameters.language = language;
                    }
                    else {
                        parameters['accept-language'] = null;
                    }
                    var body = { message : message };
                    var promise = null;
                    if (parameters.language) {
                        promise = client.apis.localize_message_v1.localize_parameterized_message_using_language(parameters, { requestBody : body });
                    }
                    else {
                        promise = client.apis.localize_message_v1.localize_parameterized_message(parameters, { requestBody : body });
                    }
                    return promise
                        .then(
                            result => Promise.resolve(result),
                            // Low level error
                            reason => {
                                if (attempt < MAX_ATTEMPTS) {
                                    return this.sleep(attempt * TIME)
                                        .then(r => this.getLocalizedMessage(message, language, attempt + 1) );
                                }
                                else {
                                    return Promise.reject(reason);
                                }
                            });
                },
                // Error: Failed to load OpenAPI specification from url
                reason => {
                    console.error(`Failed to load OpenAPI specification from url: ${this._config.url} due to: ${reason}`);
                    return Promise.reject(reason);
                })
            .then(
                result => {
                    if (result.status == 200) {
                        return result.body.text;
                    }
                    // API request failed
                    else {
                        return this._normalizeStatus('localize_message', result.status, result.body);
                    }
                },
                // Low level error
                reason => {
                    reason.api = 'localize_message';
                    console.error(`Failed to localize message due to: ${reason}`);
                    return Promise.reject(reason);
                });
    }
    /**
     * Determines the environment from the URL in the SDK's base configuration.
	 * The method will default to `PRODUCTION` if an error occurs or the environment can't be determined.
     * 
     * @private
     * 
     * @returns {String}    `PRODUCTION` or `TEST`
     */
    Transpayrent.prototype.getEnvironment = function () {
        try {
            const nonProductionEnvironments = new Array('dev', 'tst', 'test', 'stg', 'staging', 'sbx', 'sandbox');
            var environment = this._config.url.split('.')[1];
            if (environment == null) {
                console.log(`Unable to determine environment from: ${this._config.url}, defaulting to PRODUCTION`);
                return 'PRODUCTION';
            }
            // Production doesn't have a sub-domain to specify the environment
            else if (environment == 'transpayrent') {
                return 'PRODUCTION';
            }
            // Non-Production environment: Dev, Test, Sandbox etc.
            else if (nonProductionEnvironments.includes(environment) ) {
                return 'TEST';
            }
            // Local environment
            else if (environment.startsWith('localhost') ) {
                return 'TEST';
            }
            else {
                console.log(`Unable to determine environment from: ${this._config.url}, defaulting to PRODUCTION`);
                return 'PRODUCTION';
            }
        }
        catch (e) {
            console.log(`Unable to determine environment from: ${this._config.url}, defaulting to PRODUCTION`);
            return 'PRODUCTION';
        }
    }
    /**
     * Instantiates and returns a helper class for the specified payment method, which exposes the following methods:
     *  - displayPaymentButton
     *  - displayPaymentSheet
     * Helper classes are generally used for 3rd party wallet such as Apple Pay or Google Pay, which supply their own SDKs.
     * 
     * @see {@link TranspayrentGooglePayHelper}
     * 
     * @private
     * 
     * @param {PAYMENT_METHOD_ID} paymentMethodId           The payment method selected by the consumer
     * @param {InitializePaymentResponse} initialization    Optional initialization response from invoking {@link Transpayrent#initialize} for the specified payment method
     * @returns {TranspayrentGooglePayHelper}               The instantiated helper class
     */
    Transpayrent.prototype.getHelper = function (paymentMethodId, initialization) {
        const normalize = function (sdk, paymentMethodId, initialization) {
            if (initialization.connection == null) {
                initialization.connection = { action : 4,
                                              data : { merchant_id : initialization.merchant_id ? initialization.merchant_id : sdk._config.merchantId,
                                                       merchant_name : initialization.merchant_name,
                                                       merchant_origin : initialization.merchant_origin ? initialization.merchant_origin : new URL(window.location.href).hostname,
                                                       country : initialization.country,
                                                       payment_method_ids : initialization.payment_method_ids ? initialization.payment_method_ids : '103,108,109' } }
            }
            var amount = null;
            if (initialization.amount) {
                amount = { currency : sdk._currencies[initialization.amount.currency],
                           currency_id : initialization.amount.currency,
                           value : initialization.amount.value };
            }
            else if (sdk._transactionRequestCache[paymentMethodId]) {
                amount = { currency : sdk._currencies[sdk._transactionRequestCache[paymentMethodId].amount.currency],
                           currency_id : sdk._transactionRequestCache[paymentMethodId].amount.currency,
                           value : sdk._transactionRequestCache[paymentMethodId].amount.value };
            }
            const config = { merchantId : sdk._config.merchantId,
                             externalMerchantId : initialization.connection.data.merchant_id,
                             merchantName : initialization.connection.data.merchant_name,
                             merchantOrigin : initialization.connection.data.merchant_origin ? initialization.connection.data.merchant_origin : new URL(window.location.href).hostname,
                             container : initialization.container,
                             css : initialization.css,
                             callback : initialization.callback };
            const transaction = { paymentMethodIds : typeof session === 'string' ? initialization.connection.data.payment_method_ids.split(',') : initialization.connection.data.payment_method_ids,
                                  country : sdk._countries[parseInt(initialization.connection.data.country)],
                                  amount : amount,
                                  save : initialization.save };
            return { config : config,
                     transaction : transaction };
        };
        switch (parseInt(paymentMethodId) ) {
            case 203:   // Google Pay
                if (initialization) {
                    const data = normalize(this, paymentMethodId, initialization);
                    return new TranspayrentGooglePayHelper(this.getEnvironment(), data.config, data.transaction);
                }
                else {
                    return new TranspayrentGooglePayHelper(this.getEnvironment() );
                }
            case 204:   // Apple Pay
                if (initialization) {
                    const data = normalize(this, paymentMethodId, initialization);
                    return new TranspayrentApplePayHelper(this.getEnvironment(), data.config, data.transaction);
                }
                else {
                    const config = { merchantId : this._config.merchantId }
                    return new TranspayrentApplePayHelper(this.getEnvironment(), config);
                }
            default:
                return null;
        }
    }
    /**
     * Displays the payment button for the specified payment method.
     * Clicking the payment will display the  payment sheet for a 3rd party wallet such as Apple Pay or Google Pay.
     * 
     * @public
     * 
     * @example <caption>Display the "Buy with Google Pay" button using the Transpayrent SDK</caption>
     *  var container = document.getElementById('payment-methods');
     *  var paymentMethods = new Map([
     *           [ 203, 'Google Pay'],
     *           [ 103, 'VISA Dankort'],
     *           [ 107, 'Maestro'],
     *           [ 108, 'MasterCard'],
     *           [ 109, 'VISA'],
     *           [ 110, 'VISA Electron'] ]);
     * 
     *  // Display the Payment Sheet for Google Pay
     *  // See: https://developers.google.com/pay/api/web/reference/request-objects#ButtonOptions
     *  function onGooglePayLoaded() {
     *      var config = { buttonColor : 'black',
     *                     buttonType : 'buy',
     *                     onClick : () => {
     *                          const paymentMethodId = 203;                                    // Google Pay
     *                          var paymentSheetDetails = { merchant_id : transpayrentConfig.merchantId,
     *                                                      merchant_name : '[MY MERCHANT]',
     *                                                      country : 208,                      // Denmark
     *                                                      payment_method_ids : Array.from(paymentMethods.keys() ),
     *                                                      amount : { currency : 208,          // DKK
     *                                                                 value : 1000 },
     *                                                      save : false };
     *                           sdk.displayPaymentSheet(paymentMethodId, paymentSheetDetails)
     *                              .then(token => completePayment(paymentMethodId) )
     *                              .catch(reason => {
     *                                  document.getElementById('container').style.visibility = 'hidden';
     *                                  alert('API: '+ reason.api +' failed with HTTP Status Code: '+ reason.status +' and error: '+ reason.messages[0].message +'('+ reason.messages[0].code +')');
     *                              });
     *                   } };
     *      sdk.displayPaymentButton(203, container, Array.from(paymentMethods.keys() ), config);
     *  }
     * 
     * @param {PAYMENT_METHOD_ID} paymentMethodId   The payment method selected by the consumer
     * @param {...any} args                         Arguments passed to {@link TranspayrentGooglePayHelper#displayPaymentButton}
     */
    Transpayrent.prototype.displayPaymentButton = function (paymentMethodId, ...args) {
        const helper = this.getHelper(paymentMethodId);
        helper.displayPaymentButton.apply(helper, args);
    }
    /**
     * A representation of the details that will be passed to the 3rd party wallet when displaying the payment sheet.
     * Please refer to the [online documentation](/api-docs/index.html) for details on the object properties.
     * 
     * @see [COUNTRY](/v1/model/common/country/country_v1.yaml#/COUNTRY)
     * @see [Amount](/v1/model/common/amount/amount_v1.yaml#/Amount)
     * @see [CURRENCY](/v1/model/common/currency/currency_v1.yaml#/CURRENCY)
     * 
     * @typedef {Object} PaymentSheetDetails
     * @property {Integer} merchant_id                          Transpayrent's unique ID for the merchant
     * @property {String} merchant_name                         The name of the merchant which will be displayed on the payment sheet by the 3rd party wallet
     * @property {COUNTRY} country                              The numeric ISO-3166 code for the consumer's country
     * @property {Array.<PAYMENT_METHOD_ID>} payment_method_ids List of Payment Method IDs returned in the response from "Create Payment Session"
     * @property {Amount} amount                                The amount the consumer will be charged
     * @property {Boolean} save                                 Flag indicating whether the consumer's payment card will be securely stored in Transpayrent's Secure Vault
     * @property {Function} callback                            An optional callback function which will be invoked just before the authentication challenge is initiated and just after the challenge is completed.
     *                                                          The callback function accepts the arguments: `event`: The event that triggered the callback, will be either `payment-initialization-initiated` or `payment-initialization-completed`
     */
    /**
     * Displays the payment sheet for the specified payment method using an instance of its helper class.  
     * The payment sheet will allow the consumer to select a payment card from a 3rd party wallet such as Apple Pay or Google Pay.  
     * Please note that 3rd party wallets, such as Apple Pay and Google Pay, that rely on the [Payment Request API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API}
     * prohibits the payment sheet from being displayed asynchronously, which requires this method to be called from within an [async function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function}
     * using the [await operator]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await}.
     * 
     * @see {@link Transpayrent#getHelper}
     * 
     * @public
     * 
     * @example <caption>Display the Payment Sheet for Apple Pay using the Transpayrent SDK</caption>
     *  var paymentMethods = new Map([
     *           [ 204, 'Apple Pay'],
     *           [ 103, 'VISA Dankort'],
     *           [ 107, 'Maestro'],
     *           [ 108, 'MasterCard'],
     *           [ 109, 'VISA'],
     *           [ 110, 'VISA Electron'] ]);
     *  const paymentMethodId = 204;                                                // Apple Pay
     *  var paymentSheetDetails = { merchant_id : transpayrentConfig.merchantId,
     *                              merchant_name : '[MY MERCHANT]',
     *                              country : 208,                                  // Denmark
     *                              payment_method_ids : Array.from(paymentMethods.keys() ),
     *                              amount : { currency : 208,                      // DKK
     *                                         value : 1000 },
     *                              save : false };
     * sdk.displayPaymentSheet(paymentMethodId, paymentSheetDetails);
     * 
     * @param {PAYMENT_METHOD_ID} paymentMethodId   The payment method selected by the consumer
     * @param {PaymentSheetDetails} details         The details that will be passed to the 3rd party wallet when displaying the payment sheet
     * @returns {String}                            The `payment token` returned by the 3rd party wallet, which should be passed to {@link Transpayrent#authorize}
     */
    Transpayrent.prototype.displayPaymentSheet = function (paymentMethodId, details) {
        var resolver;
        var rejecter;
        var promise = new Promise( (resolve, reject) => {
            resolver = resolve;
            rejecter = reject;
        });
        if (details.callback) {
            details.callback('payment-initialization-initiated');
        }
        const helper = this.getHelper(paymentMethodId, details);
        helper.displayPaymentSheet(this, resolver, rejecter);
        return promise.then(
            token => {
                if (details.callback) {
                    details.callback('payment-initialization-completed');
                }
                this._tokenCache[paymentMethodId] = token;
                return token;
            },
            reason => {
                var message = { api : 'initialize_payment',
                                status : 502,
                                messages : [ { code : 640000,
                                               message : reason.message } ] };
                if (reason.statusCode == 'CANCELED') {
                    message = { api : 'initialize_payment',
                                status : 502,
                                messages : [ { code : 620027,
                                               message : reason.message ? reason.message : reason.statusCode } ] };
                }
                else {
                    console.error(reason);
                }
                return Promise.reject(message);
            }
        );
    }
}