/**
* @copyright Transpayrent ApS 2022
* @license Common Transpayrent API license
* @version 1.0.1
*
* @class
* @classdesc The Transpayrent Helper SDK for Google Pay<sup>TM</sup> abstracts the [Google Pay Javascript Client]{@link https://developers.google.com/pay/api/web/reference/client}
* into 2 simple methods:
* * [Display the "Buy with Google Pay" button]{@link TranspayrentGooglePayHelper#displayPaymentButton}
* * [Display the payment sheet for Google Pay]{@link TranspayrentGooglePayHelper#displayPaymentSheet} allowing the consumer to select a payment card from Google Pay
*
* The Transpayrent SDK handles the secure communication with Transpayrent's Payment Gateway and transmits the Google encrypted payment data and transaction data provided by the Transpayrent Helper SDK for Google Pay.
* Generally direct interaction with the helper SDK is unnecessary and the equivalent methods from the [Transpayrent SDK]{@link Transpayrent} called be instead.
* Please refer to the following resources for additional details in regard to implementing Google Pay:
* * [Google Pay Web developer documentation]{@link https://developers.google.com/pay/api/web/}
* * [Google Pay Web integration checklist]{@link https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist}
* * [Google Pay Web Brand Guidelines]{@link https://developers.google.com/pay/api/web/guides/brand-guidelines}
*
* Configuration of Google Pay is done dynamically using data provided by the Transpayrent Payment Gateway to automatically handle the following scenarios:
* * Strong consumer authentication *(SCA)* using 3D Secure for [PAN_ONLY]{@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters} authorizations if required
* * Passing the correct `gateway` and `gatewayMerchantId` to Google Pay
* * Dynamically enable supported card networks *(MasterCard, VISA etc.)* for Google Pay based on the configured country
*
* @see [Transpayrent]{@link Transpayrent}
* @see {@link Transpayrent#displayPaymentButton}
* @see {@link Transpayrent#displayPaymentSheet}
*
* @constructor
* @description Creates a new instance of the Transpayrent SDK, which uses the Transpayrent Helper SDK for Google Pay internally to abstract the Google Pay Javascript Client.
* @example <caption>Instantiate the Transpayrent SDK and display the "Buy with Google Pay" button</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 async src="https://pay.google.com/gp/p/js/pay.js" onload="onGooglePayLoaded()"></script>
* <script>
* // SDK configuration
* 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';
*
* // Instantiate SDK
* var sdk = new Transpayrent(url, transpayrentConfig);
*
* // List of payment methods supported by Google Pay as returned in the response from "Create Payment Session"
* const paymentMethodIds = [ 103, // VISA Dankort
* 107, // Maestro
* 108, // MasterCard
* 109, // VISA
* 110 // VISA Electron
* ];
* // Display the Payment Sheet for Google Pay
* 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 : paymentMethodIds,
* 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 +')');
* });
* } };
* // Display the "Buy with Google Pay" button
* sdk.displayPaymentButton(203, document.getElementById('google-pay-button-container'), paymentMethodIds, config);
* </script>
*
* @param {String} environment The Google Pay environment, either PRODUCTION or TEST, that will be used by the helper SDK
* @param {GooglePayConfig} config The base configuration for Google Pay.
* @param {GooglePayTransaction} transaction The details for the payment transaction that will be authorized using Google Pay
*/
function TranspayrentGooglePayHelper(environment, config, transaction) {
/**
* The Google Pay environment that will be used by the helper SDK.
* Will be either PRODUCTION or TEST.
*
* @private
*
* @type {String}
*/
this._environment = environment;
/**
* Base configuration for Google Pay
*
* @typedef {Object} GooglePayConfig
* @property {Integer} merchantId Transpayrent's unique ID for the merchant.
* @property {String} merchantName 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} merchantOrigin The merchant's domain name registered with Google Pay. Default's to the domain name obtained from `window.location.href`
* @property {Element} container The container element in which the constructed "Buy with Google Pay" button will be displayed
* @property {Function} callback The callback function accepts a single argument:
* `event`: The event that triggered the callback, will be either `payment-initialization-initiated` or `payment-initialization-completed`
*/
/**
* The base configuration for Google Pay.
*
* @private
*
* @type {GooglePayConfig}
*/
this._config = config;
/**
* The details for the payment transaction that will be authorized using Google Pay
*
* @typedef {Object} GooglePayTransaction
* @property {PAYMENT_METHOD_ID[]} paymentMethodIds List of Payment IDs returned by the Transpayrent Gateway upon initializing the payment transaction
* @property {String} country The country the payment transaction takes place in
* @property {Amount} amount The payment transaction's amount that will be authorized
* @property {Boolean} save Flag indicating whether the consumer has elected to securely store the card details that will be retrieved from Google Pay upon successful authorization
*/
/**
* The details for the payment transaction that will be authorized using Google Pay
*
* @private
*
* @type {GooglePayTransaction}
*/
this._transaction = transaction;
/**
* An initialized google.payments.api.PaymentsClient object or null if not yet set
*
* @private
*
* @see {@link TranspayrentGooglePayHelper#getGooglePaymentsClient}
*/
this._client = null;
/**
* Define the version of the Google Pay API referenced when creating your configuration
*
* @private
* @constant
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#PaymentDataRequest|apiVersion in PaymentDataRequest}
*/
const baseRequest = {
apiVersion: 2,
apiVersionMinor: 0
};
/* ========== COMMON METHODS START ========== */
/**
* Return an active Google PaymentsClient or initialize the Google PaymentsClient
*
* @private
*
* @see {@link https://developers.google.com/pay/api/web/reference/client#PaymentsClient|PaymentsClient constructor}
*
* @returns {google.payments.api.PaymentsClient} Google Pay API client
*/
TranspayrentGooglePayHelper.prototype.getGooglePaymentsClient = function () {
if (this._client === null) {
this._client = new google.payments.api.PaymentsClient({environment: this._environment});
}
return this._client;
}
/**
* @private
*
* @param {PAYMENT_METHOD_ID[]} paymentMethodIds List of Payment IDs returned by the Transpayrent Gateway upon initializing the payment transaction
* @param {Boolean} save Flag indicating whether the consumer has elected to securely store the card details that will be retrieved from Google Pay upon successful authorization
* @returns {Object} PaymentDataRequest fields
*/
TranspayrentGooglePayHelper.prototype.getBaseCardPaymentMethods = function (paymentMethodIds, save) {
/**
* Describe your site's support for the CARD payment method and its required fields
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
*/
return { type: 'CARD',
parameters: {
allowedAuthMethods: save ? ["PAN_ONLY"] : ["PAN_ONLY", "CRYPTOGRAM_3DS"],
allowedCardNetworks: [...new Set(Array.from(paymentMethodIds, (id) => {
var country = '';
try {
country = this._transaction.country.toUpperCase();
}
catch (ignore) { /* Ignore as this._transaction may be null when displaying the "Buy with Google Pay" button */ }
switch (parseInt(id) ) {
case 107: // Maestro
return 'BR' == country ? 'MAESTRO' : 'MASTERCARD';
case 108: // MasterCard
return 'MASTERCARD';
case 103: // VISA Dankort
case 109: // VISA
return 'VISA';
case 110: // VISA Electron
return 'BR' == country ? 'ELECTRON' : 'VISA';
}
}).filter(name => name) )]
}
};
}
/**
* @private
*
* @param {Integer} currency The ISO-4217 numeric code for the currency
* @returns {Integer} The number of decimals for the specified currency
*/
TranspayrentGooglePayHelper.prototype.getDecimals = function (currency) {
switch (currency) {
case 108: // BIF
case 152: // CLP
case 262: // DJF
case 324: // GNF
case 352: // ISK
case 392: // JPY
case 174: // KMF
case 410: // KRW
case 600: // PYG
case 646: // RWF
case 800: // UGX
case 940: // UYI
case 704: // VND
case 548: // VUV
case 950: // XAF
case 952: // XOF
case 953: // XPF
return 0;
case 48: // BHD
case 368: // IQD
case 400: // JOD
case 414: // KWD
case 434: // LYD
case 512: // OMR
case 788: // TND
return 3;
case 990: // CLF
case 927: // UYW
return 4;
default:
return 2;
}
}
/* ========== COMMON METHODS END ========== */
/* ========== DISPLAY PAYMENT BUTTON START ========== */
/**
* Configure your site's support for payment methods supported by the Google Pay API.
*
* Each member of allowedPaymentMethods should contain only the required fields, allowing reuse of this base request
* when determining a viewer's ability to pay and later requesting a supported payment method.
*
* @private
*
* @param {PAYMENT_METHOD_ID[]} paymentMethodIds List of Payment IDs returned by the Transpayrent Gateway upon initializing the payment transaction
* @returns {Object} Google Pay API version, payment methods supported by the site
*/
TranspayrentGooglePayHelper.prototype.getGoogleIsReadyToPayRequest = function (paymentMethodIds) {
return Object.assign(
{},
baseRequest,
{
allowedPaymentMethods: [ this.getBaseCardPaymentMethods(paymentMethodIds, false) ]
}
);
}
/**
* Add a Google Pay purchase button alongside an existing checkout button.
*
* @private
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#ButtonOptions|Button options}
* @see {@link https://developers.google.com/pay/api/web/guides/brand-guidelines|Google Pay brand guidelines}
*
* @param {*} container
* @param {*} config
* @param {...any} args
*/
TranspayrentGooglePayHelper.prototype.addGooglePayButton = function (container, config, ...args) {
const paymentsClient = this.getGooglePaymentsClient();
const callback = config.onClick;
config.onClick = () => { callback(args); };
const button = paymentsClient.createButton(config);
container.appendChild(button);
}
/**
* Prefetch payment data to improve performance.
*
* @private
*
* @see {@link https://developers.google.com/pay/api/web/reference/client#prefetchPaymentData|prefetchPaymentData()}
*
* @param {PAYMENT_METHOD_ID[]} paymentMethodIds List of Payment IDs returned by the Transpayrent Gateway upon initializing the payment transaction
*/
TranspayrentGooglePayHelper.prototype.prefetchGooglePaymentData = function (paymentMethodIds) {
const paymentDataRequest = Object.assign({}, baseRequest);
paymentDataRequest.allowedPaymentMethods = [ this.getBaseCardPaymentMethods(paymentMethodIds, false) ];
// transactionInfo must be set but does not affect cache
paymentDataRequest.transactionInfo = {
totalPriceStatus: 'NOT_CURRENTLY_KNOWN',
currencyCode: 'USD'
};
const paymentsClient = this.getGooglePaymentsClient();
paymentsClient.prefetchPaymentData(paymentDataRequest);
}
/**
* Initialize Google PaymentsClient after Google-hosted JavaScript has loaded and
* display the Google Pay payment button after confirmation of the viewer's ability to pay.
* It is **strongly recommended** to use {@link Transpayrent#displayPaymentButton}.
*
* @public
*
* @see Transpayrent#displayPaymentButton
*
* @param {*} container
* @param {PAYMENT_METHOD_ID[]} paymentMethodIds List of Payment IDs returned by the Transpayrent Gateway upon initializing the payment transaction
* @param {*} config
* @param {...any} args
*/
TranspayrentGooglePayHelper.prototype.displayPaymentButton = function (container, paymentMethodIds, config, ...args) {
const helper = this;
const paymentsClient = this.getGooglePaymentsClient();
paymentsClient.isReadyToPay(this.getGoogleIsReadyToPayRequest(paymentMethodIds) )
.then(function(response) {
if (response.result) {
helper.addGooglePayButton(container, config, args);
helper.prefetchGooglePaymentData(paymentMethodIds);
}
})
.catch(function(err) {
// show error in developer console for debugging
console.error(err);
helper._rejecter(err);
});
}
/* ========== DISPLAY PAYMENT BUTTON END ========== */
/* ========== DISPLAY PAYMENT SHEET START ========== */
/**
* Configure support for the Google Pay API.
*
* @private
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#PaymentDataRequest|PaymentDataRequest}
*
* @returns {Object} PaymentDataRequest fields
*/
TranspayrentGooglePayHelper.prototype.getGooglePaymentDataRequest = function () {
const paymentDataRequest = Object.assign({}, baseRequest);
/**
* Describe your site's support for the CARD payment method including optional fields
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#CardParameters|CardParameters}
*/
paymentDataRequest.allowedPaymentMethods = [Object.assign(
{},
this.getBaseCardPaymentMethods(this._transaction.paymentMethodIds, this._transaction.save),
{
tokenizationSpecification: {
type: 'PAYMENT_GATEWAY',
parameters: {
'gateway': 'transpayrent',
'gatewayMerchantId': this._config.merchantId.toString()
}
}
}
)];
/**
* Provide Google Pay API with a payment amount, currency, and amount status
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#TransactionInfo|TransactionInfo}
* @returns {Object} transaction info, suitable for use as transactionInfo property of PaymentDataRequest
*/
paymentDataRequest.transactionInfo = {
countryCode: this._transaction.country,
currencyCode: this._transaction.amount.currency,
totalPriceStatus: 'FINAL',
totalPrice: Number(this._transaction.amount.value / Math.pow(10, this.getDecimals(this._transaction.amount.currency) ) ).toFixed(2)
};
paymentDataRequest.merchantInfo = {
merchantId: this._config.externalMerchantId.toString(),
merchantName: this._config.merchantName,
merchantOrigin: this._config.merchantOrigin,
};
return paymentDataRequest;
}
/**
* Show Google Pay payment sheet when Google Pay payment button is clicked.
* It is **strongly recommended** to use {@link Transpayrent#displayPaymentSheet}.
*
* @public
*
* @see {@link https://developers.google.com/pay/api/web/reference/response-objects#PaymentData|PaymentData object reference}
* @see Transpayrent#displayPaymentSheet
*
* @param {Function} resolver The [resolver function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve} for a promise
* @param {Function} rejecter The [rejecter function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject} for a promise
*/
TranspayrentGooglePayHelper.prototype.displayPaymentSheet = function (sdk, resolver, rejecter) {
const paymentDataRequest = this.getGooglePaymentDataRequest();
const paymentsClient = this.getGooglePaymentsClient();
paymentsClient.loadPaymentData(paymentDataRequest)
.then(function(paymentData) {
resolver(btoa(paymentData.paymentMethodData.tokenizationData.token) );
})
.catch(function(err) {
rejecter(err);
});
}
/* ========== DISPLAY PAYMENT SHEET START ========== */
}