'use strict';
var BraintreeError = require('../lib/braintree-error');
var analytics = require('../lib/analytics');
var errors = require('./errors');
var jsonClone = require('../lib/json-clone');
var Promise = require('../lib/promise');
var wrapPromise = require('@braintree/wrap-promise');
var cardTypeTransformMap = {
Visa: 'VISA',
MasterCard: 'MASTERCARD',
Discover: 'DISCOVER',
'American Express': 'AMEX'
};
/**
* Visa Checkout Address object.
* @typedef {object} VisaCheckout~Address
* @property {string} countryCode The customer's country code.
* @property {string} extendedAddress The customer's extended address.
* @property {string} firstName The customer's first name.
* @property {string} lastName The customer's last name.
* @property {string} locality The customer's locality.
* @property {string} postalCode The customer's postal code.
* @property {string} region The customer's region.
* @property {string} streetAddress The customer's street address.
* @property {string} phoneNumber The customer's phone number.
*/
/**
* Visa Checkout UserData object.
* @typedef {object} VisaCheckout~UserData
* @property {string} userEmail The customer's email address.
* @property {string} userFirstName The customer's first name.
* @property {string} userLastName The customer's last name.
* @property {string} userFullName The customer's full name.
* @property {string} userName The customer's username.
*/
/**
* Visa Checkout tokenize payload.
* @typedef {object} VisaCheckout~tokenizePayload
* @property {string} nonce The payment method nonce.
* @property {object} details Additional account details.
* @property {string} details.cardType Type of card, ex: Visa, MasterCard.
* @property {string} details.lastTwo Last two digits of card number.
* @property {string} description A human-readable description.
* @property {string} type The payment method type, always `VisaCheckoutCard`.
* @property {VisaCheckout~Address} billingAddress The customer's billing address.
* @property {VisaCheckout~Address} shippingAddress The customer's shipping address.
* @property {VisaCheckout~UserData} userData Information about the customer.
*/
/**
* @class
* @param {object} options The Visa Checkout {@link module:braintree-web/visa-checkout.create create} options.
* @description <strong>Do not use this constructor directly. Use {@link module:braintree-web/visa-checkout.create|braintree-web.visa-checkout.create} instead.</strong>
* @classdesc This class represents a Visa Checkout component produced by {@link module:braintree-web/visa-checkout.create|braintree-web/visa-checkout.create}. Instances of this class have methods for interacting with Visa Checkout's JavaScript library.
*/
function VisaCheckout(options) {
this._client = options.client;
}
function transformCardTypes(cardTypes) {
return cardTypes.reduce(function (acc, type) {
if (cardTypeTransformMap.hasOwnProperty(type)) {
return acc.concat(cardTypeTransformMap[type]);
}
return acc;
}, []);
}
/**
* Creates an `initOptions` object from the passed `options`, applying properties that Braintree needs to transact Visa Checkout.
*
* Braintree will apply these properties if they do not exist on the given `options`:
* - `apikey`
* - `externalClientId`
* - `settings.payment.cardBrands`
*
* Braintree will overwrite `settings.dataLevel = 'FULL'` to access the full payment method.
* @public
* @param {object} options The base `initOptions` that will be used to init Visa Checkout.
* @param {string} [options.apikey] The API key used to initialize Visa Checkout. When not supplied, Braintree will set this property.
* @param {string} [options.externalClientId] The external client ID key used to initialize Visa Checkout. When not supplied, Braintree will set this property.
* @param {object} [options.settings] The settings object used to initialize Visa Checkout.
* @param {string} [options.settings.dataLevel] The data level used to initialize Visa Checkout. Braintree will overwrite this property to 'FULL'.
* @param {object} [options.settings.payment] The payment object used to initialize Visa Checkout.
* @param {string[]} [options.settings.payment.cardBrands] The card brands that Visa Checkout will allow the customer to pay with. When not supplied, Braintree will set this property.
* @returns {object} `initOptions` The `initOptions` that Visa Checkout should be initialized with.
* @example
* <caption>Applying Braintree properties to initOptions</caption>
* var baseInitOptions = {
* paymentRequest: {
* currencyCode: 'USD',
* subtotal: '1.00',
* total: '1.00'
* }
* };
*
* var initOptions = visaCheckoutInstance.createInitOptions(baseInitOptions);
*
* console.log('initOptions with Braintree properties', initOptions);
*
* V.init(initOptions);
*/
VisaCheckout.prototype.createInitOptions = function (options) {
var initOptions;
var gatewayConfiguration = this._client.getConfiguration().gatewayConfiguration;
var visaCheckoutConfiguration = gatewayConfiguration.visaCheckout;
if (!options) {
throw new BraintreeError(errors.VISA_CHECKOUT_INIT_OPTIONS_REQUIRED);
}
initOptions = jsonClone(options);
initOptions.apikey = initOptions.apikey || visaCheckoutConfiguration.apikey;
initOptions.externalClientId = initOptions.externalClientId || visaCheckoutConfiguration.externalClientId;
initOptions.settings = initOptions.settings || {};
initOptions.settings.dataLevel = 'FULL';
initOptions.settings.payment = initOptions.settings.payment || {};
if (!initOptions.settings.payment.cardBrands) {
initOptions.settings.payment.cardBrands = transformCardTypes(gatewayConfiguration.visaCheckout.supportedCardTypes);
}
return initOptions;
};
/**
* Tokenizes the Visa Checkout payload, returning a payment method nonce.
* @public
* @param {object} payment The object that Visa Checkout supplies on `payment.success`.
* @param {string} payment.callid Visa Checkout transaction ID associated with this payment.
* @param {string} payment.encKey The encrypted key used to decrypt the payment data.
* @param {string} payment.encPaymentData The encrypted payment data.
* @param {callback} [callback] The second argument, <code>tokenizePayload</code> is a {@link VisaCheckout~tokenizePayload|tokenizePayload}. If no callback is provided, `tokenize` returns a promise that resolves with the {@link VisaCheckout~tokenizePayload|tokenizePayload}.
* @returns {Promise|void} Returns a promise if no callback is provided.
* @example
* V.on('payment.success', function (payment) {
* visaCheckoutInstance.tokenize(payment, function (err, tokenizePayload) {
* if (err) {
* console.error('There was an error tokenizing Visa Checkout', err);
* return;
* }
* console.log('Send tokenizePayload.nonce to your server here!', tokenizePayload);
* });
* });
*/
VisaCheckout.prototype.tokenize = function (payment) {
var self = this;
if (!payment.callid || !payment.encKey || !payment.encPaymentData) {
return Promise.reject(new BraintreeError(errors.VISA_CHECKOUT_PAYMENT_REQUIRED));
}
return this._client.request({
method: 'post',
endpoint: 'payment_methods/visa_checkout_cards',
data: {
_meta: {
source: 'visa-checkout'
},
visaCheckoutCard: {
callId: payment.callid,
encryptedPaymentData: payment.encPaymentData,
encryptedKey: payment.encKey
}
}
}).then(function (response) {
analytics.sendEvent(self._client, 'visacheckout.tokenize.succeeded');
return response.visaCheckoutCards[0];
}).catch(function (err) {
analytics.sendEvent(self._client, 'visacheckout.tokenize.failed');
return Promise.reject(new BraintreeError({
type: errors.VISA_CHECKOUT_TOKENIZATION.type,
code: errors.VISA_CHECKOUT_TOKENIZATION.code,
message: errors.VISA_CHECKOUT_TOKENIZATION.message,
details: {
originalError: err
}
}));
});
};
module.exports = wrapPromise.wrapPrototype(VisaCheckout);