/**
* @copyright Transpayrent ApS 2022
* @license Common Transpayrent API license
* @version 1.0.3
*
* @class
* @classdesc The Transpayrent Helper SDK for Apple Pay<sup>TM</sup> abstracts the [Apple Pay Javascript Client]{@link https://developers.google.com/pay/api/web/reference/client}
* into 2 simple methods:
* * [Display the "Buy with Apple Pay" button]{@link TranspayrentApplePayHelper#displayPaymentButton}
* * [Display the payment sheet for Apple Pay]{@link TranspayrentApplePayHelper#displayPaymentSheet} allowing the consumer to select a payment card from Apple 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 Apple 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 Apple Pay:
* * [Apple Pay Web developer documentation]{@link https://developers.google.com/pay/api/web/}
* * [Apple Pay Web integration checklist]{@link https://developers.google.com/pay/api/web/guides/test-and-deploy/integration-checklist}
* * [Apple Pay Web Brand Guidelines]{@link https://developers.google.com/pay/api/web/guides/brand-guidelines}
*
* Configuration of Apple 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 Apple Pay
* * Dynamically enable supported card networks *(MasterCard, VISA etc.)* for Apple 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 Apple Pay internally to abstract the Apple Pay Javascript Client.
* @example <caption>Instantiate the Transpayrent SDK and display the "Buy with Apple 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>
* // 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 Apple 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 Apple Pay
* var config = { buttonstyle : 'black',
* type : 'pay',
* locale : 'en',
* onClick : () => {
* const paymentMethodId = 204; // Apple 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 Apple Pay" button
* sdk.displayPaymentButton(204, document.getElementById('apple-pay-button-container'), paymentMethodIds, config);
* </script>
*
* @param {String} environment The Apple Pay environment, either PRODUCTION or TEST, that will be used by the helper SDK
* @param {ApplePayConfig} config The base configuration for Apple Pay.
* @param {ApplePayTransaction} transaction The details for the payment transaction that will be authorized using Apple Pay
*/
function TranspayrentApplePayHelper(environment, config, transaction) {
/**
* The Apple 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 Apple Pay
*
* @typedef {Object} ApplePayConfig
* @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 {Element} container The container element in which the constructed "Buy with Apple 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 Apple Pay.
*
* @private
*
* @type {ApplePayConfig}
*/
this._config = config;
/**
* The details for the payment transaction that will be authorized using Apple Pay
*
* @typedef {Object} ApplePayTransaction
* @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 Apple Pay upon successful authorization
*/
/**
* The details for the payment transaction that will be authorized using Apple Pay
*
* @private
*
* @type {ApplePayTransaction}
*/
this._transaction = transaction;
/* ========== COMMON METHODS START ========== */
/**
* @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 Apple Pay upon successful authorization
* @returns {Object} PaymentDataRequest fields
*/
TranspayrentApplePayHelper.prototype.getBaseCardPaymentMethods = function (paymentMethodIds, save) {
/**
* @see {@link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaypaymentrequest/1916122-supportednetworks|supportedNetworks}
*/
return { supportedNetworks: [...new Set(Array.from(paymentMethodIds, (id) => {
switch (parseInt(id) ) {
case 107: // Maestro
return 'maestro';
case 108: // MasterCard
return 'masterCard';
case 103: // VISA Dankort
case 109: // VISA
case 110: // VISA Electron
return '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
*/
TranspayrentApplePayHelper.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 ========== */
/**
* Add a Apple Pay purchase button alongside an existing checkout button.
*
* @private
*
* @see {@link https://developer.apple.com/documentation/apple_pay_on_the_web/applepaybutton|ApplePayButton}
* @see {@link https://developer.apple.com/design/human-interface-guidelines/technologies/apple-pay/buttons-and-marks/|Buttons and marks}
*
* @param {*} container
* @param {*} config
* @param {...any} args
*/
TranspayrentApplePayHelper.prototype.addApplePayButton = function (container, config, ...args) {
const callback = config.onClick;
config.onClick = () => { callback(args); };
var button = document.createElement('apple-pay-button');
Object.entries(config).forEach( ([key, value]) => {
button.setAttribute(key, value);
});
button.onclick = config.onClick;
container.appendChild(button);
}
/**
* Displays the Apple 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
*/
TranspayrentApplePayHelper.prototype.displayPaymentButton = function (container, paymentMethodIds, config, ...args) {
const helper = this;
helper.addApplePayButton(container, config, args);
}
/* ========== DISPLAY PAYMENT BUTTON END ========== */
/* ========== DISPLAY PAYMENT SHEET START ========== */
/**
* Configure support for the Apple Pay API.
*
* @private
*
* @see {@link https://developers.google.com/pay/api/web/reference/request-objects#PaymentDataRequest|PaymentDataRequest}
*
* @returns {Object} PaymentDataRequest fields
*/
TranspayrentApplePayHelper.prototype.getApplePaymentDataRequest = function () {
// Define PaymentMethodData
const paymentMethodData = [ Object.assign(
{},
{ supportedMethods : 'https://apple.com/apple-pay',
data : Object.assign(
{},
{ version : 3,
merchantIdentifier : this._config.merchantId.toString(),
merchantCapabilities : [
'supports3DS'
],
countryCode : this._transaction.country },
this.getBaseCardPaymentMethods(this._transaction.paymentMethodIds, this._transaction.save) )
} ) ];
// Define PaymentDetails
const paymentDetails = Object.assign(
{},
{ displayItems : [ { label : this._config.merchantName,
amount : { value : Number(this._transaction.amount.value / Math.pow(10, this.getDecimals(this._transaction.amount.currency) ) ).toFixed(2),
currency : this._transaction.amount.currency } } ],
total : { label : this._config.merchantName,
amount : { value : Number(this._transaction.amount.value / Math.pow(10, this.getDecimals(this._transaction.amount.currency) ) ).toFixed(2),
currency : this._transaction.amount.currency } } },
this._transaction.save ? { modifiers : [ { supportedMethods : 'https://apple.com/apple-pay',
data : { recurringPaymentRequest : { paymentDescription : 'Subscription',
regularBilling : { paymentTiming : 'recurring',
label : 'Recurring',
amount : Number(this._transaction.amount.value / 100).toFixed(2),
recurringPaymentStartDate : new Date().toISOString() },
managementURL : 'https://api-gateway.transpayrent.cloud',
tokenNotificationURL : 'https://api-gateway.transpayrent.cloud'
}
}
} ]
} : { }
);
// Define PaymentOptions
const paymentOptions = { requestPayerName : false,
requestBillingAddress : false,
requestPayerEmail : false,
requestPayerPhone : false,
requestShipping : false,
shippingType : 'shipping' };
return { paymentMethodData : paymentMethodData,
paymentDetails : paymentDetails,
paymentOptions : paymentOptions };
}
/**
* Show Apple Pay payment sheet when Apple 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 {Transpayrent} sdk An instance of the Transpayrent Browser SDK, which may be used to interact with the Transpayrent Payment Gateway
* @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
*/
TranspayrentApplePayHelper.prototype.displayPaymentSheet = function (sdk, resolver, rejecter) {
try {
const request = this.getApplePaymentDataRequest();
// Create PaymentRequest
const payment = new PaymentRequest(request.paymentMethodData, request.paymentDetails, request.paymentOptions);
payment.onmerchantvalidation = event => {
if (this._transaction.merchantSession) {
var session = this._transaction.merchantSession;
if (typeof session === 'string' || session instanceof String) {
session = JSON.parse(session);
}
event.complete(session);
}
else {
const paymentMethodId = 204;
var request = { correlation_id : this._transaction.correlationId,
amount : { currency : this._transaction.amount.currency_id,
value : this._transaction.amount.value } };
sdk.createTransaction(paymentMethodId, request)
.then(transaction => {
// Creation of Payment Transaction failed - Display status message
if (transaction.status) {
return rejecter(`API: ${transaction.api} failed with HTTP Status Code: ${transaction.status} and error: ${transaction.messages[0].message}(${transaction.messages[0].code})`);
}
else {
var request = { payment_method_id : paymentMethodId,
save : this._transaction.save,
domain : this._config.merchantOrigin };
return sdk.initialize(transaction.id, request);
}
})
.then(initialization => {
// Initialization of Payment Transaction failed - Display status message
if (initialization.status) {
return rejecter(`API: ${initialization.api} failed with HTTP Status Code: ${initialization.status} and error: ${initialization.messages[0].message}(${initialization.messages[0].code})`);
}
else {
event.complete(JSON.parse(initialization.connection.data.merchant_session) );
}
})
.catch(reason => rejecter(reason) );
}
};
payment.onpaymentmethodchange = event => {
if (event.methodDetails.type !== undefined) {
// Define PaymentDetailsUpdate based on the selected payment method.
// No updates or errors needed, pass an object with the same total.
const paymentDetailsUpdate = {
total : request.paymentDetails.total
};
event.updateWith(paymentDetailsUpdate);
}
};
payment.show()
.then(response => {
const status = 'success';
return response.complete(status)
.then(r => resolver(btoa(JSON.stringify(response.details) ) ) )
.catch(reason => rejecter(reason) );
})
.catch(reason => rejecter(reason) );
}
catch (e) {
rejecter(e);
}
}
/* ========== DISPLAY PAYMENT SHEET END ========== */
}