"use strict";
var analytics = require("../lib/analytics");
var isBrowserSupported = require("./shared/supports-venmo");
var browserDetection = require("./shared/browser-detection");
var constants = require("./shared/constants");
var errors = require("./shared/errors");
var querystring = require("../lib/querystring");
var isVerifiedDomain = require("../lib/is-verified-domain");
var methods = require("../lib/methods");
var convertMethodsToError = require("../lib/convert-methods-to-error");
var wrapPromise = require("@braintree/wrap-promise");
var BraintreeError = require("../lib/braintree-error");
var inIframe = require("../lib/in-iframe");
var ExtendedPromise = require("@braintree/extended-promise");
var getVenmoUrl = require("./shared/get-venmo-url");
var desktopWebLogin = require("./shared/web-login-backdrop");
var snakeCaseToCamelCase = require("../lib/snake-case-to-camel-case");
var urlParams = require("../lib/url-params");
var createVenmoDesktop = require("./external/");
var graphqlQueries = require("./external/queries");
var VERSION = process.env.npm_package_version;
var DEFAULT_MOBILE_POLLING_INTERVAL = 250;
var DEFAULT_MOBILE_EXPIRING_THRESHOLD = 300000;
ExtendedPromise.suppressUnhandledPromiseMessage = true;
function Venmo(options) {
var self = this;
this._allowDesktopWebLogin = options.allowDesktopWebLogin || false;
this._mobileWebFallBack = options.mobileWebFallBack || false;
this._createPromise = options.createPromise;
this._allowNewBrowserTab = options.allowNewBrowserTab !== false;
this._allowWebviews = options.allowWebviews !== false;
this._allowDesktop = options.allowDesktop === true;
this._useRedirectForIOS = options.useRedirectForIOS === true;
this._profileId = options.profileId;
this._displayName = options.displayName;
this._deepLinkReturnUrl = options.deepLinkReturnUrl;
this._ignoreHistoryChanges = options.ignoreHistoryChanges;
this._paymentMethodUsage = (options.paymentMethodUsage || "").toUpperCase();
this._shouldUseLegacyFlow = !this._paymentMethodUsage;
this._requireManualReturn = options.requireManualReturn === true;
this._useDesktopQRFlow =
this._allowDesktop && this._isDesktop() && !this._allowDesktopWebLogin;
this._useAllowDesktopWebLogin =
this._allowDesktopWebLogin && this._isDesktop();
this._cannotHaveReturnUrls = inIframe() || this._requireManualReturn;
this._allowAndroidRecreation = options.allowAndroidRecreation !== false;
this._maxRetryCount = 3;
this._collectCustomerBillingAddress =
options.collectCustomerBillingAddress || false;
this._collectCustomerShippingAddress =
options.collectCustomerShippingAddress || false;
this._isFinalAmount = options.isFinalAmount || false;
this._lineItems = options.lineItems;
this._subTotalAmount = options.subTotalAmount;
this._discountAmount = options.discountAmount;
this._taxAmount = options.taxAmount;
this._shippingAmount = options.shippingAmount;
this._totalAmount = options.totalAmount;
this._shouldCreateVenmoPaymentContext =
this._cannotHaveReturnUrls || !this._shouldUseLegacyFlow;
analytics.sendEvent(
this._createPromise,
"venmo.desktop-flow.configured." + String(Boolean(this._allowDesktop))
);
if (this.hasTokenizationResult()) {
analytics.sendEvent(
this._createPromise,
"venmo.appswitch.return-in-new-tab"
);
} else if (this._useDesktopQRFlow) {
this._createPromise = this._createPromise.then(function (client) {
var config = client.getConfiguration().gatewayConfiguration;
return createVenmoDesktop({
url:
config.assetsUrl +
"/web/" +
VERSION +
"/html/venmo-desktop-frame.html",
environment:
config.environment === "production" ? "PRODUCTION" : "SANDBOX",
profileId: self._profileId || config.payWithVenmo.merchantId,
paymentMethodUsage: self._paymentMethodUsage,
displayName: self._displayName,
Promise: Promise,
apiRequest: function (query, data) {
return client
.request({
api: "graphQLApi",
data: {
query: query,
variables: data,
},
})
.then(function (response) {
return response.data;
});
},
sendEvent: function (eventName) {
analytics.sendEvent(self._createPromise, eventName);
},
verifyDomain: isVerifiedDomain,
})
.then(function (venmoDesktopInstance) {
self._venmoDesktopInstance = venmoDesktopInstance;
analytics.sendEvent(
self._createPromise,
"venmo.desktop-flow.presented"
);
return client;
})
.catch(function () {
analytics.sendEvent(
self._createPromise,
"venmo.desktop-flow.setup-failed"
);
self._useDesktopQRFlow = false;
return client;
});
});
} else if (this._shouldCreateVenmoPaymentContext) {
this._mobilePollingInterval = DEFAULT_MOBILE_POLLING_INTERVAL;
this._mobilePollingExpiresThreshold = DEFAULT_MOBILE_EXPIRING_THRESHOLD;
this._createPromise = this._createPromise.then(function (client) {
var paymentContextPromise, webLoginPromise;
var analyticsCategory = self._cannotHaveReturnUrls
? "manual-return"
: "mobile-payment-context";
var config = client.getConfiguration();
webLoginPromise = desktopWebLogin
.setupDesktopWebLogin({
assetsUrl: config.gatewayConfiguration.assetsUrl,
debug: config.isDebug,
})
.then(function (frameServiceInstance) {
self._frameServiceInstance = frameServiceInstance;
})
.catch(function (desktopWebErr) {
return desktopWebErr;
});
self._mobilePollingContextEnvironment =
config.gatewayConfiguration.environment.toUpperCase();
paymentContextPromise = self
._createVenmoPaymentContext(client)
.then(function () {
analytics.sendEvent(
self._createPromise,
"venmo." + analyticsCategory + ".presented"
);
return client;
})
.catch(function (err) {
analytics.sendEvent(
self._createPromise,
"venmo." + analyticsCategory + ".setup-failed"
);
return Promise.reject(
new BraintreeError({
type: errors.VENMO_MOBILE_PAYMENT_CONTEXT_SETUP_FAILED.type,
code: errors.VENMO_MOBILE_PAYMENT_CONTEXT_SETUP_FAILED.code,
message: isValidationError(err)
? err.details.originalError[0].message
: errors.VENMO_MOBILE_PAYMENT_CONTEXT_SETUP_FAILED.message,
details: {
originalError: err,
},
})
);
});
return ExtendedPromise.all([webLoginPromise, paymentContextPromise])
.then(function (results) {
var paymentContextResult = results[1];
return Promise.resolve(paymentContextResult);
})
.catch(function (promiseErr) {
return Promise.reject(promiseErr);
});
});
}
}
function isValidationError(err) {
return (
err.details &&
err.details.originalError &&
err.details.originalError[0] &&
err.details.originalError[0].extensions &&
err.details.originalError[0].extensions.errorClass === "VALIDATION" &&
err.details.originalError[0].extensions.errorType === "user_error"
);
}
Venmo.prototype._createVenmoPaymentContext = function (
client,
cancelIfTokenizationInProgress
) {
var self = this;
var promise, transactionDetails;
var configuration = client.getConfiguration();
var venmoConfiguration = configuration.gatewayConfiguration.payWithVenmo;
var transactionDetailsPresent = false;
var customerClientChannel = self._useAllowDesktopWebLogin
? "NATIVE_WEB"
: "MOBILE_WEB";
if (!this._shouldCreateVenmoPaymentContext) {
return Promise.resolve();
}
if (this._shouldUseLegacyFlow) {
promise = client
.request({
api: "graphQLApi",
data: {
query: graphqlQueries.LEGACY_CREATE_PAYMENT_CONTEXT_QUERY,
variables: {
input: {
environment: this._mobilePollingContextEnvironment,
intent: "PAY_FROM_APP",
},
},
},
})
.then(function (response) {
return response
.data.createVenmoQRCodePaymentContext.venmoQRCodePaymentContext;
});
} else {
if (
(this._collectCustomerBillingAddress ||
this._collectCustomerShippingAddress) &&
!venmoConfiguration.enrichedCustomerDataEnabled
) {
return Promise.reject(new BraintreeError(errors.VENMO_ECD_DISABLED));
}
if (this._lineItems) {
this._lineItems.forEach(function (item) {
item.unitTaxAmount = item.unitTaxAmount || "0";
});
}
transactionDetails = {
subTotalAmount: this._subTotalAmount,
discountAmount: this._discountAmount,
taxAmount: this._taxAmount,
shippingAmount: this._shippingAmount,
totalAmount: this._totalAmount,
lineItems: this._lineItems,
};
transactionDetailsPresent = Object.keys(transactionDetails).some(function (
detail
) {
return transactionDetails[detail] !== undefined;
});
promise = client
.request({
api: "graphQLApi",
data: {
query: graphqlQueries.CREATE_PAYMENT_CONTEXT_QUERY,
variables: {
input: {
paymentMethodUsage: this._paymentMethodUsage,
intent: "CONTINUE",
customerClient: customerClientChannel,
isFinalAmount: this._isFinalAmount,
displayName: this._displayName,
paysheetDetails: {
collectCustomerBillingAddress:
this._collectCustomerBillingAddress,
collectCustomerShippingAddress:
this._collectCustomerShippingAddress,
transactionDetails: transactionDetailsPresent
? transactionDetails
: undefined,
},
},
},
},
})
.then(function (response) {
return response.data.createVenmoPaymentContext.venmoPaymentContext;
});
}
return promise.then(function (context) {
var expiredTime = new Date(context.expiresAt) - new Date(context.createdAt);
var refreshIn = expiredTime * 0.6666;
clearTimeout(self._refreshPaymentContextTimeout);
self._refreshPaymentContextTimeout = setTimeout(function () {
if (self._tokenizationInProgress) {
return;
}
self._createVenmoPaymentContext(client, true);
}, refreshIn);
if (cancelIfTokenizationInProgress && self._tokenizationInProgress) {
return;
}
self._venmoPaymentContextStatus = context.status;
self._venmoPaymentContextId = context.id;
});
};
Venmo.prototype.appSwitch = function (url) {
if (this._deepLinkReturnUrl) {
if (isIosWebviewInDeepLinkReturnUrlFlow()) {
analytics.sendEvent(
this._createPromise,
"venmo.appswitch.start.ios-webview"
);
window.location.href = url;
} else if (
window.popupBridge &&
typeof window.popupBridge.open === "function"
) {
analytics.sendEvent(
this._createPromise,
"venmo.appswitch.start.popup-bridge"
);
window.popupBridge.open(url);
} else {
analytics.sendEvent(this._createPromise, "venmo.appswitch.start.webview");
window.open(url);
}
} else {
analytics.sendEvent(this._createPromise, "venmo.appswitch.start.browser");
if (
browserDetection.doesNotSupportWindowOpenInIos() ||
this._shouldUseRedirectStrategy()
) {
window.location.href = url;
} else {
window.open(url);
}
}
};
Venmo.prototype.getUrl = function () {
return this._createPromise.then(
function (client) {
var configuration = client.getConfiguration();
var params = {};
var currentUrl =
this._deepLinkReturnUrl ||
window.location.href.replace(window.location.hash, "");
var venmoConfiguration = configuration.gatewayConfiguration.payWithVenmo;
var analyticsMetadata = configuration.analyticsMetadata;
var accessToken = venmoConfiguration.accessToken;
var braintreeData = {
_meta: {
version: analyticsMetadata.sdkVersion,
integration: analyticsMetadata.integration,
platform: analyticsMetadata.platform,
sessionId: analyticsMetadata.sessionId,
},
};
this._isDebug = configuration.isDebug;
this._assetsUrl = configuration.gatewayConfiguration.assetsUrl;
currentUrl = currentUrl.replace(/#*$/, "");
if (this._venmoPaymentContextId) {
if (this._shouldUseLegacyFlow) {
accessToken += "|pcid:" + this._venmoPaymentContextId;
} else {
params.resource_id = this._venmoPaymentContextId;
}
}
if (this._shouldIncludeReturnUrls() || this._useAllowDesktopWebLogin) {
if (this._useAllowDesktopWebLogin) {
currentUrl =
this._assetsUrl + "/web/" + VERSION + "/html/redirect-frame.html";
}
params["x-success"] = currentUrl + "#venmoSuccess=1";
params["x-cancel"] = currentUrl + "#venmoCancel=1";
params["x-error"] = currentUrl + "#venmoError=1";
} else {
params["x-success"] = "NOOP";
params["x-cancel"] = "NOOP";
params["x-error"] = "NOOP";
}
if (!this._allowAndroidRecreation) {
params.allowAndroidRecreation = 0;
} else {
params.allowAndroidRecreation = 1;
}
params.ua = window.navigator.userAgent;
params.braintree_merchant_id =
this._profileId || venmoConfiguration.merchantId;
params.braintree_access_token = accessToken;
params.braintree_environment = venmoConfiguration.environment;
params.braintree_sdk_data = btoa(JSON.stringify(braintreeData));
return (
getVenmoUrl({
useAllowDesktopWebLogin: this._useAllowDesktopWebLogin,
mobileWebFallBack: this._mobileWebFallBack,
}) +
"?" +
querystring.stringify(params)
);
}.bind(this)
);
};
Venmo.prototype.isBrowserSupported = function () {
return isBrowserSupported.isBrowserSupported({
allowNewBrowserTab: this._allowNewBrowserTab,
allowWebviews: this._allowWebviews,
allowDesktop: this._allowDesktop,
allowDesktopWebLogin: this._allowDesktopWebLogin,
});
};
Venmo.prototype.hasTokenizationResult = function () {
return this._hasTokenizationResult();
};
Venmo.prototype._hasTokenizationResult = function (hash) {
var params = getFragmentParameters(hash);
var paramsFromUrl = urlParams.getUrlParams();
if (paramsFromUrl.resource_id) {
this._venmoPaymentContextId = paramsFromUrl.resource_id;
}
return (
typeof (params.venmoSuccess || params.venmoError || params.venmoCancel) !==
"undefined"
);
};
Venmo.prototype._shouldIncludeReturnUrls = function () {
if (this._deepLinkReturnUrl) {
return true;
}
return !this._cannotHaveReturnUrls;
};
Venmo.prototype._isDesktop = function () {
return !(browserDetection.isIos() || browserDetection.isAndroid());
};
Venmo.prototype.tokenize = function (options) {
var self = this;
var tokenizationPromise;
options = options || {};
if (this._tokenizationInProgress === true) {
return Promise.reject(
new BraintreeError(errors.VENMO_TOKENIZATION_REQUEST_ACTIVE)
);
}
this._tokenizationInProgress = true;
if (this._useDesktopQRFlow) {
tokenizationPromise = this._tokenizeForDesktopQRFlow(options);
} else if (this._useAllowDesktopWebLogin) {
tokenizationPromise = this._tokenizeWebLoginWithRedirect();
} else if (this._cannotHaveReturnUrls) {
tokenizationPromise = this._tokenizeForMobileWithManualReturn();
} else {
tokenizationPromise =
this._tokenizeForMobileWithHashChangeListeners(options);
}
return tokenizationPromise
.then(function (payload) {
return self._createPromise
.then(function (client) {
return self._createVenmoPaymentContext(client);
})
.then(function () {
self._tokenizationInProgress = false;
return formatTokenizePayload(payload);
});
})
.catch(function (err) {
return self._createPromise
.then(function (client) {
return self._createVenmoPaymentContext(client);
})
.then(function () {
self._tokenizationInProgress = false;
return Promise.reject(err);
});
});
};
Venmo.prototype.cancelTokenization = function () {
if (!this._tokenizationInProgress) {
return Promise.reject(
new BraintreeError(errors.VENMO_TOKENIZATION_REQUEST_NOT_ACTIVE)
);
}
this._removeVisibilityEventListener();
if (this._tokenizePromise) {
this._tokenizePromise.reject(
new BraintreeError(errors.VENMO_TOKENIZATION_CANCELED_BY_MERCHANT)
);
}
return Promise.all([
this._cancelMobilePaymentContext(),
this._cancelVenmoDesktopContext(),
]);
};
Venmo.prototype._tokenizeWebLoginWithRedirect = function () {
var self = this;
analytics.sendEvent(self._createPromise, "venmo.tokenize.web-login.start", {
paypal_context_id: self._venmoPaymentContextId,
});
this._tokenizePromise = new ExtendedPromise();
return this.getUrl().then(function (url) {
desktopWebLogin
.runWebLogin({
checkForStatusChange:
self._checkPaymentContextStatusAndProcessResult.bind(self),
cancelTokenization: self.cancelTokenization.bind(self),
frameServiceInstance: self._frameServiceInstance,
venmoUrl: url,
debug: self._isDebug,
checkPaymentContextStatus: self._checkPaymentContextStatus.bind(self),
})
.then(function (payload) {
analytics.sendEvent(
self._createPromise,
"venmo.tokenize.web-login.success",
{
paypal_context_id: self._venmoPaymentContextId,
}
);
self._tokenizePromise.resolve({
paymentMethodNonce: payload.paymentMethodId,
username: payload.userName,
payerInfo: payload.payerInfo,
id: self._venmoPaymentContextId,
});
})
.catch(function (err) {
analytics.sendEvent(
self._createPromise,
"venmo.tokenize.web-login.failure",
{
paypal_context_id: self._venmoPaymentContextId,
}
);
self._tokenizePromise.reject(err);
});
return self._tokenizePromise;
});
};
Venmo.prototype._queryPaymentContextStatus = function (id) {
var self = this;
return this._createPromise
.then(function (client) {
var query = self._shouldUseLegacyFlow
? graphqlQueries.LEGACY_VENMO_PAYMENT_CONTEXT_STATUS_QUERY
: graphqlQueries.VENMO_PAYMENT_CONTEXT_STATUS_QUERY;
return client.request({
api: "graphQLApi",
data: {
query: query,
variables: {
id: id,
},
},
});
})
.then(function (response) {
return response.data.node;
});
};
Venmo.prototype._checkPaymentContextStatusAndProcessResult = function (
retryCount
) {
var self = this;
return self._checkPaymentContextStatus().then(function (node) {
var resultStatus = node.status;
if (resultStatus !== self._venmoPaymentContextStatus) {
self._venmoPaymentContextStatus = resultStatus;
analytics.sendEvent(
self._createPromise,
"venmo.tokenize.web-login.status-change",
{
paypal_context_id: self._venmoPaymentContextId,
}
);
switch (resultStatus) {
case "APPROVED":
return Promise.resolve(node);
case "CANCELED":
return Promise.reject(
new BraintreeError(errors.VENMO_CUSTOMER_CANCELED)
);
case "FAILED":
return Promise.reject(
new BraintreeError(errors.VENMO_TOKENIZATION_FAILED)
);
default:
}
}
return new Promise(function (resolve, reject) {
if (retryCount < self._maxRetryCount) {
retryCount++;
return self
._checkPaymentContextStatusAndProcessResult(retryCount)
.then(resolve)
.catch(reject);
}
return reject(new BraintreeError(errors.VENMO_TOKENIZATION_FAILED));
});
});
};
Venmo.prototype._checkPaymentContextStatus = function () {
var self = this;
return self
._queryPaymentContextStatus(self._venmoPaymentContextId)
.catch(function (networkError) {
return Promise.reject(
new BraintreeError({
type: errors.VENMO_NETWORK_ERROR.type,
code: errors.VENMO_NETWORK_ERROR.code,
message: errors.VENMO_NETWORK_ERROR.message,
details: networkError,
})
);
})
.then(function (node) {
return Promise.resolve(node);
});
};
Venmo.prototype._pollForStatusChange = function () {
var self = this;
if (Date.now() > self._mobilePollingContextExpiresIn) {
return Promise.reject(
new BraintreeError(errors.VENMO_MOBILE_POLLING_TOKENIZATION_TIMEOUT)
);
}
return this._queryPaymentContextStatus(this._venmoPaymentContextId)
.catch(function (networkError) {
return Promise.reject(
new BraintreeError({
type: errors.VENMO_MOBILE_POLLING_TOKENIZATION_NETWORK_ERROR.type,
code: errors.VENMO_MOBILE_POLLING_TOKENIZATION_NETWORK_ERROR.code,
message:
errors.VENMO_MOBILE_POLLING_TOKENIZATION_NETWORK_ERROR.message,
details: {
originalError: networkError,
},
})
);
})
.then(function (node) {
var newStatus = node.status;
if (newStatus !== self._venmoPaymentContextStatus) {
self._venmoPaymentContextStatus = newStatus;
analytics.sendEvent(
self._createPromise,
"venmo.tokenize.manual-return.status-change." +
newStatus.toLowerCase(),
{
paypal_context_id: self._venmoPaymentContextId,
}
);
switch (newStatus) {
case "EXPIRED":
case "FAILED":
case "CANCELED":
return Promise.reject(
new BraintreeError(
errors["VENMO_MOBILE_POLLING_TOKENIZATION_" + newStatus]
)
);
case "APPROVED":
return Promise.resolve(node);
case "CREATED":
case "SCANNED":
default:
}
}
return new Promise(function (resolve, reject) {
setTimeout(function () {
self._pollForStatusChange().then(resolve).catch(reject);
}, self._mobilePollingInterval);
});
});
};
Venmo.prototype._tokenizeForMobileWithManualReturn = function () {
var self = this;
analytics.sendEvent(
this._createPromise,
"venmo.tokenize.manual-return.start",
{
paypal_context_id: self._venmoPaymentContextId,
}
);
this._mobilePollingContextExpiresIn =
Date.now() + this._mobilePollingExpiresThreshold;
this._tokenizePromise = new ExtendedPromise();
this._pollForStatusChange()
.then(function (payload) {
analytics.sendEvent(
self._createPromise,
"venmo.tokenize.manual-return.success",
{
paypal_context_id: self._venmoPaymentContextId,
}
);
self._tokenizePromise.resolve({
paymentMethodNonce: payload.paymentMethodId,
username: payload.userName,
payerInfo: payload.payerInfo,
id: self._venmoPaymentContextId,
});
})
.catch(function (err) {
analytics.sendEvent(
self._createPromise,
"venmo.tokenize.manual-return.failure",
{
paypal_context_id: self._venmoPaymentContextId,
}
);
self._tokenizePromise.reject(err);
});
return this.getUrl().then(function (url) {
self.appSwitch(url);
return self._tokenizePromise;
});
};
Venmo.prototype._shouldUseRedirectStrategy = function () {
if (!browserDetection.isIos()) {
return false;
}
if (this._mobileWebFallBack === true) {
return true;
}
return this._useRedirectForIOS;
};
Venmo.prototype._tokenizeForMobileWithHashChangeListeners = function (options) {
var self = this;
var resultProcessingInProgress, visibilityChangeListenerTimeout;
if (this.hasTokenizationResult()) {
return this.processHashChangeFlowResults();
}
analytics.sendEvent(this._createPromise, "venmo.tokenize.mobile.start");
this._tokenizePromise = new ExtendedPromise();
this._previousHash = window.location.hash;
function completeFlow(hash) {
var error;
self
.processHashChangeFlowResults(hash)
.catch(function (err) {
error = err;
})
.then(function (res) {
if (
!self._ignoreHistoryChanges &&
window.location.hash !== self._previousHash
) {
window.location.hash = self._previousHash;
}
self._removeVisibilityEventListener();
if (error) {
self._tokenizePromise.reject(error);
} else {
self._tokenizePromise.resolve(res);
}
delete self._tokenizePromise;
});
}
this._onHashChangeListener = function (e) {
var hash = e.newURL.split("#")[1];
if (!self._hasTokenizationResult(hash)) {
return;
}
resultProcessingInProgress = true;
clearTimeout(visibilityChangeListenerTimeout);
completeFlow(hash);
};
window.addEventListener("hashchange", this._onHashChangeListener, false);
this._visibilityChangeListener = function () {
var delay =
options.processResultsDelay || constants.DEFAULT_PROCESS_RESULTS_DELAY;
if (!window.document.hidden) {
if (!resultProcessingInProgress) {
visibilityChangeListenerTimeout = setTimeout(completeFlow, delay);
}
}
};
return this.getUrl().then(function (url) {
self.appSwitch(url);
setTimeout(function () {
window.document.addEventListener(
documentVisibilityChangeEventName(),
self._visibilityChangeListener
);
}, constants.DOCUMENT_VISIBILITY_CHANGE_EVENT_DELAY);
return self._tokenizePromise;
});
};
Venmo.prototype._tokenizeForDesktopQRFlow = function () {
var self = this;
analytics.sendEvent(this._createPromise, "venmo.tokenize.desktop.start");
this._tokenizePromise = new ExtendedPromise();
this._createPromise
.then(function () {
return self._venmoDesktopInstance.launchDesktopFlow();
})
.then(function (payload) {
self._venmoDesktopInstance.hideDesktopFlow();
analytics.sendEvent(
self._createPromise,
"venmo.tokenize.desktop.success"
);
self._tokenizePromise.resolve(payload);
})
.catch(function (err) {
analytics.sendEvent(
self._createPromise,
"venmo.tokenize.desktop.failure"
);
if (self._venmoDesktopInstance) {
self._venmoDesktopInstance.hideDesktopFlow();
}
if (err && err.reason === "CUSTOMER_CANCELED") {
self._tokenizePromise.reject(
new BraintreeError(errors.VENMO_DESKTOP_CANCELED)
);
return;
}
self._tokenizePromise.reject(
new BraintreeError({
type: errors.VENMO_DESKTOP_UNKNOWN_ERROR.type,
code: errors.VENMO_DESKTOP_UNKNOWN_ERROR.code,
message: errors.VENMO_DESKTOP_UNKNOWN_ERROR.message,
details: {
originalError: err,
},
})
);
});
return this._tokenizePromise;
};
Venmo.prototype._cancelMobilePaymentContext = function () {
var self = this;
return this._createPromise.then(function (client) {
var query;
if (self._venmoPaymentContextId) {
query = self._shouldUseLegacyFlow
? graphqlQueries.LEGACY_UPDATE_PAYMENT_CONTEXT_QUERY
: graphqlQueries.UPDATE_PAYMENT_CONTEXT_QUERY;
return client.request({
api: "graphQLApi",
data: {
query: query,
variables: {
input: {
id: self._venmoPaymentContextId,
status: "CANCELED",
},
},
},
});
}
return Promise.resolve();
});
};
Venmo.prototype._cancelVenmoDesktopContext = function () {
var self = this;
return this._createPromise.then(function () {
if (self._venmoDesktopInstance) {
self._venmoDesktopInstance.updateVenmoDesktopPaymentContext("CANCELED");
}
return Promise.resolve();
});
};
Venmo.prototype.teardown = function () {
var self = this;
this._removeVisibilityEventListener();
return this._createPromise.then(
function () {
if (self._venmoDesktopInstance) {
self._venmoDesktopInstance.teardown();
}
clearTimeout(self._refreshPaymentContextTimeout);
self._cancelMobilePaymentContext();
convertMethodsToError(this, methods(Venmo.prototype));
}.bind(this)
);
};
Venmo.prototype._removeVisibilityEventListener = function () {
window.removeEventListener("hashchange", this._onHashChangeListener);
window.document.removeEventListener(
documentVisibilityChangeEventName(),
this._visibilityChangeListener
);
delete this._visibilityChangeListener;
delete this._onHashChangeListener;
};
Venmo.prototype.processHashChangeFlowResults = function (hash) {
var self = this;
var params = getFragmentParameters(hash);
return new Promise(function (resolve, reject) {
if (!self._shouldUseLegacyFlow) {
self
._pollForStatusChange()
.then(function (payload) {
analytics.sendEvent(
self._createPromise,
"venmo.appswitch.handle.payment-context-status-query.success",
{
paypal_context_id: self._venmoPaymentContextId,
}
);
return resolve({
paymentMethodNonce: payload.paymentMethodId,
username: payload.userName,
payerInfo: payload.payerInfo,
id: self._venmoPaymentContextId,
});
})
.catch(function (err) {
if (
err.type === errors.VENMO_MOBILE_POLLING_TOKENIZATION_CANCELED.type
) {
reject(err);
}
analytics.sendEvent(
self._createPromise,
"venmo.process-results.payment-context-status-query-failed",
{
paypal_context_id: self._venmoPaymentContextId,
}
);
resolve(params);
});
} else if (params.venmoSuccess) {
analytics.sendEvent(
self._createPromise,
"venmo.appswitch.handle.success"
);
resolve(params);
} else if (params.venmoError) {
analytics.sendEvent(self._createPromise, "venmo.appswitch.handle.error");
reject(
new BraintreeError({
type: errors.VENMO_APP_FAILED.type,
code: errors.VENMO_APP_FAILED.code,
message: errors.VENMO_APP_FAILED.message,
details: {
originalError: {
message: decodeURIComponent(params.errorMessage),
code: params.errorCode,
},
},
})
);
} else if (params.venmoCancel) {
analytics.sendEvent(self._createPromise, "venmo.appswitch.handle.cancel");
reject(new BraintreeError(errors.VENMO_APP_CANCELED));
} else {
analytics.sendEvent(
self._createPromise,
"venmo.appswitch.cancel-or-unavailable"
);
reject(new BraintreeError(errors.VENMO_CANCELED));
}
self._clearFragmentParameters();
});
};
Venmo.prototype._clearFragmentParameters = function () {
if (this._ignoreHistoryChanges) {
return;
}
if (
typeof window.history.replaceState === "function" &&
window.location.hash
) {
history.pushState(
{},
"",
window.location.href.slice(0, window.location.href.indexOf("#"))
);
}
};
function getFragmentParameters(hash) {
var keyValuesArray = (hash || window.location.hash.substring(1)).split("&");
var parsedParams = keyValuesArray.reduce(function (toReturn, keyValue) {
var parts = keyValue.split("=");
var decodedKey = decodeURIComponent(parts[0]).replace(/\W/g, "");
var key = snakeCaseToCamelCase(decodedKey);
var value = decodeURIComponent(parts[1]);
toReturn[key] = value;
return toReturn;
}, {});
if (parsedParams.resourceId) {
parsedParams.id = parsedParams.resourceId;
}
return parsedParams;
}
function formatUserName(username) {
username = username || "";
return "@" + username.replace("@", "");
}
function formatTokenizePayload(payload) {
var formattedPayload = {
nonce: payload.paymentMethodNonce,
type: "VenmoAccount",
details: {
username: formatUserName(payload.username),
paymentContextId: payload.id,
},
};
if (payload.payerInfo) {
formattedPayload.details.payerInfo = payload.payerInfo;
formattedPayload.details.payerInfo.userName = formatUserName(
payload.payerInfo.userName
);
}
return formattedPayload;
}
function documentVisibilityChangeEventName() {
var visibilityChange;
if (typeof window.document.hidden !== "undefined") {
visibilityChange = "visibilitychange";
} else if (typeof window.document.msHidden !== "undefined") {
visibilityChange = "msvisibilitychange";
} else if (typeof window.document.webkitHidden !== "undefined") {
visibilityChange = "webkitvisibilitychange";
}
return visibilityChange;
}
function isIosWebviewInDeepLinkReturnUrlFlow() {
return (
window.navigator.platform &&
/iPhone|iPad|iPod/.test(window.navigator.platform)
);
}
module.exports = wrapPromise.wrapPrototype(Venmo);