/**
* @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 : 0, // 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);
}
);
}
}