Repository: braintree/braintree-web-drop-in
Pull request #600 (2020-03-05 02:52:09)

Remove reliance on client
### Summary

Speeds up performance by removing the need for Drop-in to wait for a client to set up.

### Checklist

- [x] Added a changelog entry

### Authors
> List GitHub usernames for everyone who contributed to this pull request.

- @crookedneighbor 
- @sshropshire 
- @Epreuve 


crookedneighbor: > WHEW! So much happening. There's some IE9 & IE10 related code that should be deleted, but I'm assuming that's disregarded for now to be handled on the v2_breaking_changes branch later.
>
> (on that note though IE11 supports const. Let's run codemods once this PR is merged)
TIL

> Most of the questions I came up with ended up being answered later in the code, but there is definitely enough going on here that I'm a little worried I missed something. I'm gonna assume we encountered no problems with the useDefferedClient typo due to the fact that we don't do integration testing, but have these changes been tested in a demo app of any kind?

We do have integration testing happening!

We need to do manual testing for:
* Google Pay
* Apple Pay
* 3D Secure

The main thing is that the `useDeferredClient` bit hasn't actually been released in the underlying sdk (for Appel Pay and google Pay at least). Currently, those components will just pause the setup to setup the client before continuing if an authorization is passed into them instead of the client. For the components that only have async methods that rely on the client, we can swap out that behavior for an actual deferred client. For a few components, they need the `useDeferredClient` flag to change the signature of a few synchronous components to be async and wait for the client to be ready.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2494c48..67f6a3f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,10 +3,26 @@ CHANGELOG
 
 2.0.0 - unreleased
 -----
-* TODO: list new features here
+* PayPal can now override global autovaulting preferences with `paypal.vault.autoVault`
 
 _Breaking Changes_
-* TODO: list them here
+* Drop support for IE 9-10
+* Whenever a new payment method is added, all other unvaulted payment methods will be removed from available payment methods
+* Payment methods will no longer automatically be hidden or error on setup if merchant is not enabled for them
+* Credit card view changes
+  * `card` configuration changes
+    - `card.vault.vaultCard` is now `card.vault.autoVault`
+    - `card.vault.allowVaultCardOverride` is now `card.vault.allowAutoVaultOverride`
+    - Previously, CVV would only be presented as a field if merchant had the [CVV rule configured in the control panel](https://articles.braintreepayments.com/guides/fraud-tools/basic/avs-cvv-rules). Now CVV will always be presented unless it is specifically removed by passing `card.cvv = { collect: false }`
+    - Previously, postal code would only be presented as a field if merchant had the [postal code AVS rule configured in the control panel](https://articles.braintreepayments.com/guides/fraud-tools/basic/avs-cvv-rules). Now postal code will not be presented unless it is specifically enabled by passing `card.postalCode = { collect: true }`
+* Vault Management changes
+  * `vaultManager` configuration changes
+    - cannot be used with tokenization keys
+    - create options is now an object instead of a boolean
+    - can opt out of `vaultManager.autoVaultPaymentMethods`
+    - can opt out of `vaultManager.presentVaultedPaymentMethods`
+    - can opt into `vaultManager.allowCustomerToDeletePaymentMethods`
+    - `preselectVaultedPaymentMethod` has been moved out of a top level and into `vaultManager`
 
 unreleased
 ----------
diff --git a/package.json b/package.json
index bb7ea33..b57a93c 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
     "build:gh-pages": "gulp build:gh-pages",
     "lint": "eslint src test",
     "hint": "hint http://localhost:4567",
-    "pretest": "npm run lint",
+    "posttest": "npm run lint",
     "test": "jest",
     "test:hint": "npm-run-all -n -s build -pr server hint",
     "test:integration": "wdio wdio.conf.js",
diff --git a/src/constants.js b/src/constants.js
index 7bb43ed..1cfeb9c 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -1,6 +1,10 @@
 'use strict';
 
 module.exports = {
+  authorizationTypes: {
+    CLIENT_TOKEN: 'CLIENT_TOKEN',
+    TOKENIZATION_KEY: 'TOKENIZATION_KEY'
+  },
   paymentOptionIDs: {
     card: 'card',
     paypal: 'paypal',
@@ -35,14 +39,12 @@ module.exports = {
     Maestro: 'maestro'
   },
   configurationCardTypes: {
-    visa: 'Visa',
-    'master-card': 'MasterCard',
-    'american-express': 'American Express',
-    'diners-club': 'Discover',
-    discover: 'Discover',
-    jcb: 'JCB',
-    unionpay: 'UnionPay',
-    maestro: 'Maestro'
+    Visa: 'visa',
+    Mastercard: 'mastercard',
+    'American Express': 'american-express',
+    Discover: 'discover',
+    JCB: 'jcb',
+    Maestro: 'maestro'
   },
   errors: {
     NO_PAYMENT_METHOD_ERROR: 'No payment method is available.',
diff --git a/src/dropin-model.js b/src/dropin-model.js
index 3bb7741..6eaec2e 100644
--- a/src/dropin-model.js
+++ b/src/dropin-model.js
@@ -1,13 +1,14 @@
 'use strict';
 
 var analytics = require('./lib/analytics');
+var assign = require('./lib/assign').assign;
 var DropinError = require('./lib/dropin-error');
 var EventEmitter = require('@braintree/event-emitter');
 var constants = require('./constants');
 var paymentMethodTypes = constants.paymentMethodTypes;
 var paymentOptionIDs = constants.paymentOptionIDs;
-var isGuestCheckout = require('./lib/is-guest-checkout');
 var Promise = require('./lib/promise');
+var parseAuthorization = require('./lib/parse-authorization');
 var paymentSheetViews = require('./views/payment-sheet-views');
 var vaultManager = require('braintree-web/vault-manager');
 
@@ -24,17 +25,32 @@ var DEFAULT_PAYMENT_OPTION_PRIORITY = [
   paymentOptionIDs.applePay,
   paymentOptionIDs.googlePay
 ];
+var DEFAULT_VAULT_MANAGER_SETTINGS_FOR_AUTH_WITH_CUSTOMER_ID = {
+  autoVaultPaymentMethods: true,
+  presentVaultedPaymentMethods: true,
+  preselectVaultedPaymentMethod: true,
+  allowCustomerToDeletePaymentMethods: false
+};
+var DEFAULT_VAULT_MANAGER_SETTINGS_FOR_AUTH_WITHOUT_CUSTOMER_ID = {
+  autoVaultPaymentMethods: false,
+  presentVaultedPaymentMethods: false,
+  preselectVaultedPaymentMethod: false,
+  allowCustomerToDeletePaymentMethods: false
+};
 
 function DropinModel(options) {
+  var parsedAuthorization = parseAuthorization(options.merchantConfiguration.authorization);
+
   this.componentID = options.componentID;
   this.merchantConfiguration = options.merchantConfiguration;
-
-  this.isGuestCheckout = isGuestCheckout(options.client);
+  this.environment = parsedAuthorization.environment;
+  this.hasCustomer = parsedAuthorization.hasCustomer;
+  this.authType = parsedAuthorization.authType;
+  this.authorization = options.merchantConfiguration.authorization;
 
   this.dependenciesInitializing = 0;
   this.dependencySuccessCount = 0;
   this.failedDependencies = {};
-  this._options = options;
   this._setupComplete = false;
 
   EventEmitter.call(this);
@@ -45,12 +61,30 @@ EventEmitter.createChild(DropinModel);
 DropinModel.prototype.initialize = function () {
   var self = this;
 
+  if (this.authType === constants.authorizationTypes.CLIENT_TOKEN) {
+    analytics.sendEvent('started.client-token');
+  } else {
+    analytics.sendEvent('started.tokenization-key');
+  }
+  if (this.hasCustomer) {
+    this.vaultManagerConfig = assign({}, DEFAULT_VAULT_MANAGER_SETTINGS_FOR_AUTH_WITH_CUSTOMER_ID, this.merchantConfiguration.vaultManager);
+  } else {
+    if (this.merchantConfiguration.vaultManager) {
+      return Promise.reject(new DropinError('vaultManager cannot be used with tokenization keys.'));
+    }
+    this.vaultManagerConfig = assign({}, DEFAULT_VAULT_MANAGER_SETTINGS_FOR_AUTH_WITHOUT_CUSTOMER_ID);
+  }
+
   return vaultManager.create({
-    client: self._options.client
+    authorization: self.authorization
   }).then(function (vaultManagerInstance) {
     self._vaultManager = vaultManagerInstance;
 
-    return getSupportedPaymentOptions(self._options);
+    return getSupportedPaymentOptions({
+      environment: self.environment,
+      authType: self.authType,
+      merchantConfiguration: self.merchantConfiguration
+    });
   }).then(function (paymentOptions) {
     self.supportedPaymentOptions = paymentOptions;
 
@@ -86,6 +120,16 @@ DropinModel.prototype.removePaymentMethod = function (paymentMethod) {
   this._emit('removePaymentMethod', paymentMethod);
 };
 
+DropinModel.prototype.removeUnvaultedPaymentMethods = function (filter) {
+  filter = filter || function () { return true; };
+
+  this.getPaymentMethods().forEach(function (paymentMethod) {
+    if (filter(paymentMethod) && !paymentMethod.vaulted) {
+      this.removePaymentMethod(paymentMethod);
+    }
+  }.bind(this));
+};
+
 DropinModel.prototype.refreshPaymentMethods = function () {
   var self = this;
 
@@ -121,7 +165,7 @@ DropinModel.prototype.selectPaymentOption = function (paymentViewID) {
 };
 
 DropinModel.prototype.enableEditMode = function () {
-  analytics.sendEvent(this._options.client, 'manager.appeared');
+  analytics.sendEvent('manager.appeared');
   this._isInEditMode = true;
   this._emit('enableEditMode');
 };
@@ -265,7 +309,7 @@ DropinModel.prototype.deleteVaultedPaymentMethod = function () {
 
   this._emit('startVaultedPaymentMethodDeletion');
 
-  if (!self.isGuestCheckout) {
+  if (this._paymentMethodWaitingToBeDeleted.vaulted) {
     promise = this._vaultManager.deletePaymentMethod(this._paymentMethodWaitingToBeDeleted.nonce).catch(function (err) {
       error = err;
     });
@@ -290,7 +334,7 @@ DropinModel.prototype.cancelDeleteVaultedPaymentMethod = function () {
 DropinModel.prototype.getVaultedPaymentMethods = function () {
   var self = this;
 
-  if (self.isGuestCheckout) {
+  if (!self.vaultManagerConfig.presentVaultedPaymentMethods) {
     return Promise.resolve([]);
   }
 
@@ -368,7 +412,7 @@ function isPaymentOptionEnabled(paymentOption, options) {
   }
 
   return SheetView.isEnabled({
-    client: options.client,
+    environment: options.environment,
     merchantConfiguration: options.merchantConfiguration
   }).catch(function (error) {
     console.error(SheetView.ID + ' view errored when checking if it was supported.'); // eslint-disable-line no-console
diff --git a/src/dropin.js b/src/dropin.js
index 1816a7d..f00cc51 100644
--- a/src/dropin.js
+++ b/src/dropin.js
@@ -25,6 +25,8 @@ var wrapPrototype = require('@braintree/wrap-promise').wrapPrototype;
 var mainHTML = fs.readFileSync(__dirname + '/html/main.html', 'utf8');
 var svgHTML = fs.readFileSync(__dirname + '/html/svgs.html', 'utf8');
 
+var ASSETS_URL = 'https://assets.braintreegateway.com';
+
 var UPDATABLE_CONFIGURATION_OPTIONS = [
   paymentOptionIDs.paypal,
   paymentOptionIDs.paypalCredit,
@@ -264,7 +266,6 @@ HAS_RAW_PAYMENT_DATA[constants.paymentMethodTypes.applePay] = true;
  * @classdesc This class represents a Drop-in component, that will create a pre-made UI for accepting cards and PayPal on your page. Instances of this class have methods for requesting a payment method and subscribing to events. For more information, see the [Drop-in guide](https://developers.braintreepayments.com/guides/drop-in/javascript/v3) in the Braintree Developer Docs. To be used in conjunction with the [Braintree Server SDKs](https://developers.braintreepayments.com/start/hello-server/).
  */
 function Dropin(options) {
-  this._client = options.client;
   this._componentID = uuid();
   this._dropinWrapper = document.createElement('div');
   this._dropinWrapper.id = 'braintree--dropin__' + this._componentID;
@@ -272,6 +273,7 @@ function Dropin(options) {
   this._dropinWrapper.style.display = 'none';
   this._dropinWrapper.className = 'braintree-loading';
   this._merchantConfiguration = options.merchantConfiguration;
+  this._authorization = this._merchantConfiguration.authorization;
 
   EventEmitter.call(this);
 }
@@ -286,12 +288,12 @@ Dropin.prototype._initialize = function (callback) {
   self._injectStylesheet();
 
   if (!container) {
-    analytics.sendEvent(self._client, 'configuration-error');
+    analytics.sendEvent('configuration-error');
     callback(new DropinError('options.container is required.'));
 
     return;
   } else if (self._merchantConfiguration.container && self._merchantConfiguration.selector) {
-    analytics.sendEvent(self._client, 'configuration-error');
+    analytics.sendEvent('configuration-error');
     callback(new DropinError('Must only have one options.selector or options.container.'));
 
     return;
@@ -302,14 +304,14 @@ Dropin.prototype._initialize = function (callback) {
   }
 
   if (!container || container.nodeType !== 1) {
-    analytics.sendEvent(self._client, 'configuration-error');
+    analytics.sendEvent('configuration-error');
     callback(new DropinError('options.selector or options.container must reference a valid DOM node.'));
 
     return;
   }
 
   if (container.innerHTML.trim()) {
-    analytics.sendEvent(self._client, 'configuration-error');
+    analytics.sendEvent('configuration-error');
     callback(new DropinError('options.selector or options.container must reference an empty DOM node.'));
 
     return;
@@ -344,7 +346,6 @@ Dropin.prototype._initialize = function (callback) {
   container.appendChild(self._dropinWrapper);
 
   self._model = new DropinModel({
-    client: self._client,
     componentID: self._componentID,
     merchantConfiguration: self._merchantConfiguration
   });
@@ -352,13 +353,13 @@ Dropin.prototype._initialize = function (callback) {
   self._model.initialize().then(function () {
     self._model.on('cancelInitialization', function (err) {
       self._dropinWrapper.innerHTML = '';
-      analytics.sendEvent(self._client, 'load-error');
+      analytics.sendEvent('load-error');
       callback(err);
     });
 
     self._model.on('asyncDependenciesReady', function () {
       if (self._model.dependencySuccessCount >= 1) {
-        analytics.sendEvent(self._client, 'appeared');
+        analytics.sendEvent('appeared');
         self._disableErroredPaymentMethods();
 
         self._handleAppSwitch();
@@ -430,7 +431,7 @@ Dropin.prototype.updateConfiguration = function (property, key, value) {
     return;
   }
 
-  this._removeUnvaultedPaymentMethods(function (paymentMethod) {
+  this._model.removeUnvaultedPaymentMethods(function (paymentMethod) {
     return paymentMethod.type === constants.paymentMethodTypes[property];
   });
   this._navigateToInitialView();
@@ -461,7 +462,7 @@ Dropin.prototype.updateConfiguration = function (property, key, value) {
  * });
  */
 Dropin.prototype.clearSelectedPaymentMethod = function () {
-  this._removeUnvaultedPaymentMethods();
+  this._model.removeUnvaultedPaymentMethods();
   this._model.removeActivePaymentMethod();
 
   if (this._model.getPaymentMethods().length === 0) {
@@ -480,7 +481,7 @@ Dropin.prototype.clearSelectedPaymentMethod = function () {
 
 Dropin.prototype._setUpDataCollector = function () {
   var self = this;
-  var config = assign({}, self._merchantConfiguration.dataCollector, {client: self._client});
+  var config = assign({}, self._merchantConfiguration.dataCollector, {authorization: self._authorization});
 
   this._model.asyncDependencyStarting();
   this._dataCollector = new DataCollector(config);
@@ -501,7 +502,7 @@ Dropin.prototype._setUpThreeDSecure = function () {
 
   this._model.asyncDependencyStarting();
 
-  this._threeDSecure = new ThreeDSecure(this._client, config);
+  this._threeDSecure = new ThreeDSecure(this._authorization, config);
 
   this._threeDSecure.initialize().then(function () {
     self._model.asyncDependencyReady();
@@ -523,23 +524,12 @@ Dropin.prototype._setUpDependenciesAndViews = function () {
   }
 
   this._mainView = new MainView({
-    client: this._client,
     element: this._dropinWrapper,
     model: this._model,
     strings: this._strings
   });
 };
 
-Dropin.prototype._removeUnvaultedPaymentMethods = function (filter) {
-  filter = filter || function () { return true; };
-
-  this._model.getPaymentMethods().forEach(function (paymentMethod) {
-    if (filter(paymentMethod) && !paymentMethod.vaulted) {
-      this._model.removePaymentMethod(paymentMethod);
-    }
-  }.bind(this));
-};
-
 Dropin.prototype._navigateToInitialView = function () {
   var hasNoSavedPaymentMethods, hasOnlyOneSupportedPaymentOption;
   var isOnMethodsView = this._mainView.primaryView.ID === paymentMethodsViewID;
@@ -603,7 +593,7 @@ Dropin.prototype._sendVaultedPaymentMethodAppearAnalyticsEvents = function () {
 
     typesThatSentAnEvent[type] = true;
 
-    analytics.sendEvent(this._client, 'vaulted-' + constants.analyticsKinds[type] + '.appear');
+    analytics.sendEvent('vaulted-' + constants.analyticsKinds[type] + '.appear');
   }
 };
 
@@ -718,7 +708,11 @@ Dropin.prototype.requestPaymentMethod = function (options) {
     return payload;
   }).then(function (payload) {
     if (self._dataCollector) {
-      payload.deviceData = self._dataCollector.getDeviceData();
+      return self._dataCollector.getDeviceData().then(function (deviceData) {
+        payload.deviceData = deviceData;
+
+        return payload;
+      });
     }
 
     return payload;
@@ -736,12 +730,11 @@ Dropin.prototype._removeStylesheet = function () {
 };
 
 Dropin.prototype._injectStylesheet = function () {
-  var stylesheetUrl, assetsUrl;
+  var stylesheetUrl;
 
   if (document.getElementById(constants.STYLESHEET_ID)) { return; }
 
-  assetsUrl = this._client.getConfiguration().gatewayConfiguration.assetsUrl;
-  stylesheetUrl = assetsUrl + '/web/dropin/' + VERSION + '/css/dropin@DOT_MIN.css';
+  stylesheetUrl = ASSETS_URL + '/web/dropin/' + VERSION + '/css/dropin@DOT_MIN.css';
 
   assets.loadStylesheet({
     href: stylesheetUrl,
diff --git a/src/html/card-icons.html b/src/html/card-icons.html
index c2a6bb0..d3c84e1 100644
--- a/src/html/card-icons.html
+++ b/src/html/card-icons.html
@@ -1,11 +1,11 @@
-<div data-braintree-id="visa-card-icon" class="braintree-sheet__card-icon">
+<div data-braintree-id="visa-card-icon" class="braintree-sheet__card-icon braintree-hidden">
     <svg width="40" height="24">
         <use xlink:href="#icon-visa"></use>
     </svg>
 </div>
-<div data-braintree-id="master-card-card-icon" class="braintree-sheet__card-icon">
+<div data-braintree-id="mastercard-card-icon" class="braintree-sheet__card-icon braintree-hidden">
     <svg width="40" height="24">
-        <use xlink:href="#icon-master-card"></use>
+        <use xlink:href="#icon-mastercard"></use>
     </svg>
 </div>
 <div data-braintree-id="unionpay-card-icon" class="braintree-sheet__card-icon braintree-hidden">
@@ -13,12 +13,12 @@
         <use xlink:href="#icon-unionpay"></use>
     </svg>
 </div>
-<div data-braintree-id="american-express-card-icon" class="braintree-sheet__card-icon">
+<div data-braintree-id="american-express-card-icon" class="braintree-sheet__card-icon braintree-hidden">
     <svg width="40" height="24">
         <use xlink:href="#icon-american-express"></use>
     </svg>
 </div>
-<div data-braintree-id="jcb-card-icon" class="braintree-sheet__card-icon">
+<div data-braintree-id="jcb-card-icon" class="braintree-sheet__card-icon braintree-hidden">
     <svg width="40" height="24">
         <use xlink:href="#icon-jcb"></use>
     </svg>
@@ -29,12 +29,12 @@
         <use xlink:href="#icon-diners-club"></use>
     </svg>
 </div>
-<div data-braintree-id="discover-card-icon" class="braintree-sheet__card-icon">
+<div data-braintree-id="discover-card-icon" class="braintree-sheet__card-icon braintree-hidden">
     <svg width="40" height="24">
         <use xlink:href="#icon-discover"></use>
     </svg>
 </div>
-<div data-braintree-id="maestro-card-icon" class="braintree-sheet__card-icon">
+<div data-braintree-id="maestro-card-icon" class="braintree-sheet__card-icon braintree-hidden">
     <svg width="40" height="24">
         <use xlink:href="#icon-maestro"></use>
     </svg>
diff --git a/src/index.js b/src/index.js
index 5f7f206..5736771 100644
--- a/src/index.js
+++ b/src/index.js
@@ -104,9 +104,7 @@
  */
 
 var Dropin = require('./dropin');
-var client = require('braintree-web/client');
 var createFromScriptTag = require('./lib/create-from-script-tag');
-var constants = require('./constants');
 var analytics = require('./lib/analytics');
 var DropinError = require('./lib/dropin-error');
 var Promise = require('./lib/promise');
@@ -123,8 +121,8 @@ var VERSION = '__VERSION__';
  * @param {object} [overrides.styles] The Hosted Fields [`styles` options](http://braintree.github.io/braintree-web/{@pkg bt-web-version}/module-braintree-web_hosted-fields.html#~styleOptions). These can be used to add custom styles to the Hosted Fields iframes. To style the rest of Drop-in, [review the documentation for customizing Drop-in](https://developers.braintreepayments.com/guides/drop-in/customization/javascript/v3#customize-your-ui).
  * @param {boolean} [clearFieldsAfterTokenization=true] When false, the card form will not clear the card data when the customer returns to the card view after a successful tokenization.
  * @param {object} [vault] Configuration for vaulting credit cards. Only applies when using a [client token with a customer id](https://developers.braintreepayments.com/reference/request/client-token/generate/#customer_id).
- * @param {boolean} [vault.allowVaultCardOverride=false] When true, the card form will include an option to let the customer decide not to vault the credit card they enter.
- * @param {boolean} [vault.vaultCard=true] Whether or not to vault the card upon tokenization. When set to `false` with `allowVaultCardOverride` set to `false`, then cards will not be vaulted.
+ * @param {boolean} [vault.allowAutoVaultOverride=false] When true, the card form will include an option to let the customer decide not to vault the credit card they enter.
+ * @param {boolean} [vault.autoVault=true] Whether or not to vault the card upon tokenization. When set to `false` with `allowVaultCardOverride` set to `false`, then cards will not be vaulted. Can be used to opt in or opt of the global [`vaultManager` settings](#~vaultManagerCreateOptions) for autovaulting cards.
  */
 
 /**
@@ -147,6 +145,8 @@ var VERSION = '__VERSION__';
  * @param {string} [currency] The currency code of the amount, such as `USD`. Required when using the Checkout flow.
  * @param {string} [buttonStyle] The style object to apply to the PayPal button. Button customization includes color, shape, size, and label. The options [found here](https://developer.paypal.com/docs/integration/direct/express-checkout/integration-jsv4/customize-button/#button-styles) are available.
  * @param {boolean} [commit] The user action to show on the PayPal review page. If true, a `Pay Now` button will be shown. If false, a `Continue` button will be shown.
+ * @param {object} [vault] Options for how vaulting should be handled with PayPal accounts.
+ * @param {boolean} [vault.autoVault] Can be used to opt in or opt of the global [`vaultManager` settings](#~vaultManagerCreateOptions) for autovaulting.
  */
 
 /** @typedef {object} applePayCreateOptions The configuration options for Apple Pay.
@@ -165,6 +165,35 @@ var VERSION = '__VERSION__';
  * @param {external:GooglePayButtonOptions} [button] The button options for configuring the look of the Google Pay button. The `onClick` property cannot be overwritten.
  */
 
+/** @typedef {object} vaultManagerCreateOptions Options for managing the customer's vault. Must be used with a [client token with a customer id](https://developers.braintreepayments.com/reference/request/client-token/generate/#customer_id).
+ *
+ * @param {boolean} [autoVaultPaymentMethods=true]  Auto-vault payment methods as they are added by the customer. This can be opted out on a per payment method basis as well.
+ * @param {boolean} [presentVaultedPaymentMethods=true] Present a customer's saved payment methods.
+ * @param {boolean} [preselectVaultedPaymentMethod=true] Initialize Drop-in with a vaulted payment method pre-selected. Only applicable when `presentVaultedPaymentMethods` is set to `true` and the customer has saved payment methods.
+ * @param {boolean} [allowCustomerToDeletePaymentMethods=false] Allow customer to delete saved payment methods. *Note:* Deleting a payment method from Drop-in will permanently delete the payment method, so this option is not recommended for merchants using Braintree's recurring billing system.
+ *
+ * @example <caption>Overriding global auto-vaulting behavior</caption>
+ * // will autovault all payment methods except for PayPal
+ * braintree.dropin.create({
+ *   vaultManager: {
+ *     autoVaultPaymentMethods: true // can also just omit this line
+ *   },
+ *   paypal: {
+ *     autoVault: false
+ *   }
+ * });
+ *
+ * // will only autovault PayPal
+ * braintree.dropin.create({
+ *   vaultManager: {
+ *     autoVaultPaymentMethods: false
+ *   },
+ *   paypal: {
+ *     autoVault: true
+ *   }
+ * });
+ */
+
 /**
  * @typedef {object} ApplePayPaymentRequest An [Apple Pay Payment Request object](https://developer.apple.com/reference/applepayjs/1916082-applepay_js_data_types/paymentrequest).
  * @external ApplePayPaymentRequest
@@ -251,11 +280,11 @@ var VERSION = '__VERSION__';
  *
  * @param {object} [options.dataCollector] The configuration options for data collector. See [`dataCollectorOptions`](#~dataCollectorOptions) for all `dataCollector` options. If Data Collector is configured and fails to load, Drop-in creation will fail.
  *
- * @param {(boolean|object)} [options.threeDSecure] It's recomended that you pass `true` here to enable 3D Secure and pass the configuration options for 3D Secure into {@link Dropin#requestPaymentMethod|requestPaymentMethod options}.See [`threeDSecureOptions`](#~threeDSecureOptions) for the deprecated create options. If 3D Secure is configured and fails to load, Drop-in creation will fail.
+ * @param {(boolean|object)} [options.threeDSecure] It's recommended that you pass `true` here to enable 3D Secure and pass the configuration options for 3D Secure into {@link Dropin#requestPaymentMethod|requestPaymentMethod options}.See [`threeDSecureOptions`](#~threeDSecureOptions) for the deprecated create options. If 3D Secure is configured and fails to load, Drop-in creation will fail.
  *
- * @param {boolean} [options.vaultManager=false] Whether or not to allow a customer to delete saved payment methods when used with a [client token with a customer id](https://developers.braintreepayments.com/reference/request/client-token/generate/#customer_id). *Note:* Deleting a payment method from Drop-in will permanently delete the payment method, so this option is not recommended for merchants using Braintree's recurring billing system. This feature is not supported in Internet Explorer 9.
+ * @param {object} [options.vaultManager] The configuration options for vault management.
  *
- * @param {boolean} [options.preselectVaultedPaymentMethod=true] Whether or not to initialize Drop-in with a vaulted payment method pre-selected. Only applicable when using a [client token with a customer id](https://developers.braintreepayments.com/reference/request/client-token/generate/#customer_id) and a customer with saved payment methods.
+ * See [`vaultManagerCreateOptions`](#~vaultManagerCreateOptions) for `vaultManager` options.
  *
  * @param {function} [callback] The second argument, `data`, is the {@link Dropin} instance. Returns a promise if no callback is provided.
  * @returns {(void|Promise)} Returns a promise if no callback is provided.
@@ -553,53 +582,23 @@ function create(options) {
     return Promise.reject(new DropinError('options.authorization is required.'));
   }
 
-  return client.create({
-    authorization: options.authorization
-  }).catch(function (err) {
-    return Promise.reject(new DropinError({
-      message: 'There was an error creating Drop-in.',
-      braintreeWebError: err
-    }));
-  }).then(function (clientInstance) {
-    clientInstance = setAnalyticsIntegration(clientInstance);
-
-    if (clientInstance.getConfiguration().authorizationType === 'TOKENIZATION_KEY') {
-      analytics.sendEvent(clientInstance, 'started.tokenization-key');
-    } else {
-      analytics.sendEvent(clientInstance, 'started.client-token');
-    }
+  analytics.setupAnalytics(options.authorization);
 
-    return new Promise(function (resolve, reject) {
-      new Dropin({
-        merchantConfiguration: options,
-        client: clientInstance
-      })._initialize(function (err, instance) {
-        if (err) {
-          reject(err);
+  return new Promise(function (resolve, reject) {
+    new Dropin({
+      merchantConfiguration: options
+    })._initialize(function (err, instance) {
+      if (err) {
+        reject(err);
 
-          return;
-        }
+        return;
+      }
 
-        resolve(instance);
-      });
+      resolve(instance);
     });
   });
 }
 
-function setAnalyticsIntegration(clientInstance) {
-  var configuration = clientInstance.getConfiguration();
-
-  configuration.analyticsMetadata.integration = constants.INTEGRATION;
-  configuration.analyticsMetadata.integrationType = constants.INTEGRATION;
-  configuration.analyticsMetadata.dropinVersion = VERSION;
-
-  clientInstance.getConfiguration = function () {
-    return configuration;
-  };
-
-  return clientInstance;
-}
-
 // we check for document's existence to support server side rendering
 createFromScriptTag(create, typeof document !== 'undefined' && document.querySelector('script[data-braintree-dropin-authorization]'));
 
diff --git a/src/lib/__mocks__/.eslintrc b/src/lib/__mocks__/.eslintrc
new file mode 100644
index 0000000..55f121d
--- /dev/null
+++ b/src/lib/__mocks__/.eslintrc
@@ -0,0 +1,5 @@
+{
+  "env": {
+    "jest": true
+  }
+}
diff --git a/src/lib/__mocks__/analytics.js b/src/lib/__mocks__/analytics.js
new file mode 100644
index 0000000..e162e9b
--- /dev/null
+++ b/src/lib/__mocks__/analytics.js
@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = {
+  sendEvent: jest.fn(),
+  setupAnalytics: jest.fn()
+};
diff --git a/src/lib/analytics.js b/src/lib/analytics.js
index 88eba6a..2e6f5eb 100644
--- a/src/lib/analytics.js
+++ b/src/lib/analytics.js
@@ -1,41 +1,81 @@
 'use strict';
 
-var atob = require('./polyfill').atob;
+var btClient = require('braintree-web/client');
 var constants = require('../constants');
+var Promise = require('./promise');
 var braintreeClientVersion = require('braintree-web/client').VERSION;
+var VERSION = '__VERSION__';
+
+var clientPromise;
 
 function _millisToSeconds(millis) {
   return Math.floor(millis / 1000);
 }
 
-function sendAnalyticsEvent(client, kind, callback) {
-  var configuration = client.getConfiguration();
-  var analyticsRequest = client._request;
+function setupAnalytics(authorization) {
+  clientPromise = btClient.create({
+    authorization: authorization
+  }).then(function (clientInstance) {
+    var configuration = clientInstance.getConfiguration();
+
+    configuration.analyticsMetadata.integration = constants.INTEGRATION;
+    configuration.analyticsMetadata.integrationType = constants.INTEGRATION;
+    configuration.analyticsMetadata.dropinVersion = VERSION;
+
+    clientInstance.getConfiguration = function () {
+      return configuration;
+    };
+
+    return clientInstance;
+  });
+
+  return clientPromise;
+}
+
+function resetClientPromise() {
+  clientPromise = null;
+}
+
+function sendAnalyticsEvent(kind) {
   var timestamp = _millisToSeconds(Date.now());
-  var url = configuration.gatewayConfiguration.analytics.url;
-  var data = {
-    analytics: [{
-      kind: constants.ANALYTICS_PREFIX + kind,
-      timestamp: timestamp
-    }],
-    _meta: configuration.analyticsMetadata,
-    braintreeLibraryVersion: braintreeClientVersion
-  };
-
-  if (configuration.authorizationType === 'TOKENIZATION_KEY') {
-    data.tokenizationKey = configuration.authorization;
-  } else {
-    data.authorizationFingerprint = JSON.parse(atob(configuration.authorization)).authorizationFingerprint;
+
+  if (!clientPromise) {
+    return Promise.reject(new Error('Client not available.'));
   }
 
-  analyticsRequest({
-    url: url,
-    method: 'post',
-    data: data,
-    timeout: constants.ANALYTICS_REQUEST_TIMEOUT_MS
-  }, callback);
+  return clientPromise.then(function (client) {
+    var configuration = client.getConfiguration();
+    var url = configuration.gatewayConfiguration.analytics.url;
+    var data = {
+      analytics: [{
+        kind: constants.ANALYTICS_PREFIX + kind,
+        timestamp: timestamp
+      }],
+      _meta: configuration.analyticsMetadata,
+      braintreeLibraryVersion: braintreeClientVersion
+    };
+
+    if (configuration.authorizationType === 'TOKENIZATION_KEY') {
+      data.tokenizationKey = configuration.authorization;
+    } else {
+      data.authorizationFingerprint = JSON.parse(window.atob(configuration.authorization)).authorizationFingerprint;
+    }
+
+    return new Promise(function (resolve) {
+      client._request({
+        url: url,
+        method: 'post',
+        data: data,
+        timeout: constants.ANALYTICS_REQUEST_TIMEOUT_MS
+      }, function () {
+        resolve();
+      });
+    });
+  });
 }
 
 module.exports = {
-  sendEvent: sendAnalyticsEvent
+  resetClientPromise: resetClientPromise,
+  sendEvent: sendAnalyticsEvent,
+  setupAnalytics: setupAnalytics
 };
diff --git a/src/lib/create-from-script-tag.js b/src/lib/create-from-script-tag.js
index 3b6dd0d..16b1383 100644
--- a/src/lib/create-from-script-tag.js
+++ b/src/lib/create-from-script-tag.js
@@ -5,7 +5,7 @@ var find = require('./find-parent-form');
 var uuid = require('./uuid');
 var DropinError = require('./dropin-error');
 var kebabCaseToCamelCase = require('./kebab-case-to-camel-case');
-var WHITELISTED_DATA_ATTRIBUTES = [
+var ALLOWLISTED_DATA_ATTRIBUTES = [
   'locale',
   'payment-option-priority',
 
@@ -97,7 +97,7 @@ function createFromScriptTag(createFunction, scriptTag) {
     container: container
   };
 
-  WHITELISTED_DATA_ATTRIBUTES.forEach(function (compositeKey) {
+  ALLOWLISTED_DATA_ATTRIBUTES.forEach(function (compositeKey) {
     var value = scriptTag.getAttribute('data-' + compositeKey);
 
     if (value == null) {
@@ -108,7 +108,7 @@ function createFromScriptTag(createFunction, scriptTag) {
   });
 
   createFunction(createOptions).then(function (instance) {
-    analytics.sendEvent(instance._client, 'integration-type.script-tag');
+    analytics.sendEvent('integration-type.script-tag');
     form.addEventListener('submit', function () {
       instance.requestPaymentMethod(function (requestPaymentError, payload) {
         if (requestPaymentError) {
diff --git a/src/lib/data-collector.js b/src/lib/data-collector.js
index 69954dc..ca7eb70 100644
--- a/src/lib/data-collector.js
+++ b/src/lib/data-collector.js
@@ -1,26 +1,26 @@
 'use strict';
 
+var braintreeWebVersion = require('braintree-web/client').VERSION;
+var assign = require('./assign').assign;
 var constants = require('../constants');
 var analytics = require('./analytics');
 var assets = require('@braintree/asset-loader');
 var Promise = require('./promise');
 
 function DataCollector(config) {
-  this._config = config;
+  this._config = assign(config, {
+    useDeferredClient: true
+  });
 }
 
 DataCollector.prototype.initialize = function () {
   var self = this;
 
   return Promise.resolve().then(function () {
-    var braintreeWebVersion;
-
     if (global.braintree && global.braintree.dataCollector) {
       return Promise.resolve();
     }
 
-    braintreeWebVersion = self._config.client.getVersion();
-
     return assets.loadScript({
       src: 'https://js.braintreegateway.com/web/' + braintreeWebVersion + '/js/data-collector.min.js',
       id: constants.DATA_COLLECTOR_SCRIPT_ID
@@ -30,7 +30,11 @@ DataCollector.prototype.initialize = function () {
   }).then(function (instance) {
     self._instance = instance;
   }).catch(function (err) {
-    analytics.sendEvent(self._config.client, 'data-collector.setup-failed');
+    // TODO we need a way to bubble up errors
+    // back to the merchant in the case where
+    // something goes wrong when setting up data collector
+    // instead of silently failing
+    analytics.sendEvent('data-collector.setup-failed');
     // log the Data Collector setup error
     // but do not prevent Drop-in from loading
     self.log(err);
@@ -43,10 +47,10 @@ DataCollector.prototype.log = function (message) {
 
 DataCollector.prototype.getDeviceData = function () {
   if (!this._instance) {
-    return '';
+    return Promise.resolve('');
   }
 
-  return this._instance.deviceData;
+  return this._instance.getDeviceData();
 };
 
 DataCollector.prototype.teardown = function () {
diff --git a/src/lib/is-guest-checkout.js b/src/lib/is-guest-checkout.js
deleted file mode 100644
index 571bf30..0000000
--- a/src/lib/is-guest-checkout.js
+++ /dev/null
@@ -1,16 +0,0 @@
-'use strict';
-
-var atob = require('./polyfill').atob;
-
-module.exports = function (client) {
-  var authorizationFingerprint;
-  var configuration = client.getConfiguration();
-
-  if (configuration.authorizationType !== 'TOKENIZATION_KEY') {
-    authorizationFingerprint = JSON.parse(atob(configuration.authorization)).authorizationFingerprint;
-
-    return !authorizationFingerprint || authorizationFingerprint.indexOf('customer_id=') === -1;
-  }
-
-  return true;
-};
diff --git a/src/lib/parse-authorization.js b/src/lib/parse-authorization.js
new file mode 100644
index 0000000..8cb6665
--- /dev/null
+++ b/src/lib/parse-authorization.js
@@ -0,0 +1,29 @@
+'use strict';
+
+var authorizationTypes = require('../constants').authorizationTypes;
+
+function _isTokenizationKey(str) {
+  return /^[a-zA-Z0-9]+_[a-zA-Z0-9]+_[a-zA-Z0-9_]+$/.test(str);
+}
+
+function parseEnvironment(auth) {
+  var parsedClientToken;
+
+  if (_isTokenizationKey(auth)) {
+    return {
+      authType: authorizationTypes.TOKENIZATION_KEY,
+      environment: auth.split('_')[0],
+      hasCustomer: false
+    };
+  }
+
+  parsedClientToken = JSON.parse(window.atob(auth));
+
+  return {
+    authType: authorizationTypes.CLIENT_TOKEN,
+    environment: parsedClientToken.environment,
+    hasCustomer: Boolean(parsedClientToken.hasCustomer)
+  };
+}
+
+module.exports = parseEnvironment;
diff --git a/src/lib/polyfill.js b/src/lib/polyfill.js
deleted file mode 100644
index 0b38257..0000000
--- a/src/lib/polyfill.js
+++ /dev/null
@@ -1,38 +0,0 @@
-'use strict';
-/* eslint-disable no-mixed-operators */
-
-var atobNormalized = typeof global.atob === 'function' ? global.atob : atob;
-
-function atob(base64String) {
-  var a, b, c, b1, b2, b3, b4, i;
-  var base64Matcher = new RegExp('^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})([=]{1,2})?$');
-  var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
-  var result = '';
-
-  if (!base64Matcher.test(base64String)) {
-    throw new Error('Non base64 encoded input passed to window.atob polyfill');
-  }
-
-  i = 0;
-  do {
-    b1 = characters.indexOf(base64String.charAt(i++));
-    b2 = characters.indexOf(base64String.charAt(i++));
-    b3 = characters.indexOf(base64String.charAt(i++));
-    b4 = characters.indexOf(base64String.charAt(i++));
-
-    a = (b1 & 0x3F) << 2 | b2 >> 4 & 0x3;
-    b = (b2 & 0xF) << 4 | b3 >> 2 & 0xF;
-    c = (b3 & 0x3) << 6 | b4 & 0x3F;
-
-    result += String.fromCharCode(a) + (b ? String.fromCharCode(b) : '') + (c ? String.fromCharCode(c) : '');
-  } while (i < base64String.length);
-
-  return result;
-}
-
-module.exports = {
-  atob: function (base64String) {
-    return atobNormalized.call(global, base64String);
-  },
-  _atob: atob
-};
diff --git a/src/lib/three-d-secure.js b/src/lib/three-d-secure.js
index 51a475c..affee2a 100644
--- a/src/lib/three-d-secure.js
+++ b/src/lib/three-d-secure.js
@@ -5,8 +5,8 @@ var threeDSecure = require('braintree-web/three-d-secure');
 
 var DEFAULT_ACS_WINDOW_SIZE = '03';
 
-function ThreeDSecure(client, merchantConfiguration) {
-  this._client = client;
+function ThreeDSecure(authorization, merchantConfiguration) {
+  this._authorization = authorization;
   this._config = merchantConfiguration;
 }
 
@@ -14,7 +14,7 @@ ThreeDSecure.prototype.initialize = function () {
   var self = this;
 
   return threeDSecure.create({
-    client: this._client,
+    authorization: this._authorization,
     version: 2
   }).then(function (instance) {
     self._instance = instance;
diff --git a/src/views/main-view.js b/src/views/main-view.js
index f0a25dc..a0e6445 100644
--- a/src/views/main-view.js
+++ b/src/views/main-view.js
@@ -63,7 +63,6 @@ MainView.prototype._initialize = function () {
         element: this.getElementById(PaymentSheetView.ID),
         mainView: this,
         model: this.model,
-        client: this.client,
         strings: this.strings
       });
       paymentSheetView.initialize();
@@ -78,7 +77,6 @@ MainView.prototype._initialize = function () {
   this.paymentMethodsViews = new PaymentMethodsView({
     element: this.element,
     model: this.model,
-    client: this.client,
     strings: this.strings
   });
   this.addView(this.paymentMethodsViews);
@@ -119,7 +117,6 @@ MainView.prototype._initialize = function () {
 
   if (this._hasMultiplePaymentOptions) {
     paymentOptionsView = new PaymentOptionsView({
-      client: this.client,
       element: this.getElementById(PaymentOptionsView.ID),
       mainView: this,
       model: this.model,
@@ -207,14 +204,14 @@ MainView.prototype.requestPaymentMethod = function () {
   var activePaymentView = this.getView(this.model.getActivePaymentView());
 
   return activePaymentView.requestPaymentMethod().then(function (payload) {
-    analytics.sendEvent(this.client, 'request-payment-method.' + analyticsKinds[payload.type]);
+    analytics.sendEvent('request-payment-method.' + analyticsKinds[payload.type]);
 
     return payload;
-  }.bind(this)).catch(function (err) {
-    analytics.sendEvent(this.client, 'request-payment-method.error');
+  }).catch(function (err) {
+    analytics.sendEvent('request-payment-method.error');
 
     return Promise.reject(err);
-  }.bind(this));
+  });
 };
 
 MainView.prototype.hideLoadingIndicator = function () {
@@ -380,11 +377,12 @@ MainView.prototype.finishVaultedPaymentMethodDeletion = function (error) {
 
 MainView.prototype._sendToDefaultView = function () {
   var paymentMethods = this.model.getPaymentMethods();
-  var preselectVaultedPaymentMethod = this.model.merchantConfiguration.preselectVaultedPaymentMethod !== false;
+  var vaultManagerConfig = this.model.vaultManagerConfig;
+  var preselectVaultedPaymentMethod = vaultManagerConfig.preselectVaultedPaymentMethod !== false;
 
   if (paymentMethods.length > 0) {
     if (preselectVaultedPaymentMethod) {
-      analytics.sendEvent(this.client, 'vaulted-card.preselect');
+      analytics.sendEvent('vaulted-card.preselect');
 
       this.model.changeActivePaymentMethod(paymentMethods[0]);
     } else {
diff --git a/src/views/payment-method-view.js b/src/views/payment-method-view.js
index 6588cd3..24c6f8d 100644
--- a/src/views/payment-method-view.js
+++ b/src/views/payment-method-view.js
@@ -1,10 +1,10 @@
 'use strict';
 
-var analytics = require('../lib/analytics');
 var BaseView = require('./base-view');
 var classList = require('@braintree/class-list');
 var constants = require('../constants');
 var fs = require('fs');
+var analytics = require('../lib/analytics');
 var addSelectionEventHandler = require('../lib/add-selection-event-handler');
 
 var paymentMethodHTML = fs.readFileSync(__dirname + '/../html/payment-method.html', 'utf8');
@@ -95,7 +95,7 @@ PaymentMethodView.prototype._choosePaymentMethod = function () {
     return;
   }
   if (this.paymentMethod.vaulted) {
-    analytics.sendEvent(this.client, 'vaulted-' + constants.analyticsKinds[this.paymentMethod.type] + '.select');
+    analytics.sendEvent('vaulted-' + constants.analyticsKinds[this.paymentMethod.type] + '.select');
   }
 
   this.model.changeActivePaymentMethod(this.paymentMethod);
diff --git a/src/views/payment-methods-view.js b/src/views/payment-methods-view.js
index f37accf..92e96ca 100644
--- a/src/views/payment-methods-view.js
+++ b/src/views/payment-methods-view.js
@@ -35,12 +35,11 @@ PaymentMethodsView.prototype._initialize = function () {
   this.model.on('addPaymentMethod', this._addPaymentMethod.bind(this));
   this.model.on('changeActivePaymentMethod', this._changeActivePaymentMethodView.bind(this));
   this.model.on('refreshPaymentMethods', this.refreshPaymentMethods.bind(this));
+  this.model.on('removePaymentMethod', this._removePaymentMethod.bind(this));
 
   this.refreshPaymentMethods();
 
   if (this.model.merchantConfiguration.vaultManager) {
-    this.model.on('removePaymentMethod', this._removePaymentMethod.bind(this));
-
     addSelectionEventHandler(this._editButton, function () {
       if (this.model.isInEditMode()) {
         this.model.disableEditMode();
@@ -101,14 +100,12 @@ PaymentMethodsView.prototype._addPaymentMethod = function (paymentMethod) {
   var paymentMethodView = new PaymentMethodView({
     model: this.model,
     paymentMethod: paymentMethod,
-    client: this.client,
     strings: this.strings
   });
 
-  if (this.model.isGuestCheckout && this.container.firstChild) {
-    this.container.removeChild(this.container.firstChild);
-    this.views.pop();
-  }
+  this.model.removeUnvaultedPaymentMethods(function (pm) {
+    return pm.nonce !== paymentMethod.nonce;
+  });
 
   if (this.container.firstChild) {
     this.container.insertBefore(paymentMethodView.element, this.container.firstChild);
diff --git a/src/views/payment-options-view.js b/src/views/payment-options-view.js
index 9c62192..3f7ede0 100644
--- a/src/views/payment-options-view.js
+++ b/src/views/payment-options-view.js
@@ -34,7 +34,7 @@ PaymentOptionsView.prototype._addPaymentOption = function (paymentOptionID) {
   var clickHandler = function clickHandler() {
     this.mainView.setPrimaryView(paymentOptionID);
     this.model.selectPaymentOption(paymentOptionID);
-    analytics.sendEvent(this.client, 'selected.' + paymentOptionIDs[paymentOptionID]);
+    analytics.sendEvent('selected.' + paymentOptionIDs[paymentOptionID]);
   }.bind(this);
 
   div.className = 'braintree-option braintree-option__' + paymentOptionID;
diff --git a/src/views/payment-sheet-views/apple-pay-view.js b/src/views/payment-sheet-views/apple-pay-view.js
index 5f5c58c..89273e6 100644
--- a/src/views/payment-sheet-views/apple-pay-view.js
+++ b/src/views/payment-sheet-views/apple-pay-view.js
@@ -20,7 +20,7 @@ ApplePayView.ID = ApplePayView.prototype.ID = paymentOptionIDs.applePay;
 
 ApplePayView.prototype.initialize = function () {
   var self = this;
-  var isProduction = self.client.getConfiguration().gatewayConfiguration.environment === 'production';
+  var isProduction = this.model.environment === 'production';
 
   self.applePayConfiguration = assign({}, self.model.merchantConfiguration.applePay);
   self.applePaySessionVersion = self.applePayConfiguration.applePaySessionVersion || DEFAULT_APPLE_PAY_SESSION_VERSION;
@@ -29,7 +29,10 @@ ApplePayView.prototype.initialize = function () {
 
   self.model.asyncDependencyStarting();
 
-  return btApplePay.create({client: this.client}).then(function (applePayInstance) {
+  return btApplePay.create({
+    authorization: this.model.authorization,
+    useDeferredClient: true
+  }).then(function (applePayInstance) {
     var buttonDiv = self.getElementById('apple-pay-button');
 
     self.applePayInstance = applePayInstance;
@@ -103,8 +106,7 @@ ApplePayView.prototype.updateConfiguration = function (key, value) {
 };
 
 ApplePayView.isEnabled = function (options) {
-  var gatewayConfiguration = options.client.getConfiguration().gatewayConfiguration;
-  var applePayEnabled = gatewayConfiguration.applePayWeb && Boolean(options.merchantConfiguration.applePay);
+  var applePayEnabled = Boolean(options.merchantConfiguration.applePay);
   var applePaySessionVersion = options.merchantConfiguration.applePay && options.merchantConfiguration.applePay.applePaySessionVersion;
   var applePayBrowserSupported;
 
diff --git a/src/views/payment-sheet-views/base-paypal-view.js b/src/views/payment-sheet-views/base-paypal-view.js
index ca2b35c..d658d1d 100644
--- a/src/views/payment-sheet-views/base-paypal-view.js
+++ b/src/views/payment-sheet-views/base-paypal-view.js
@@ -31,7 +31,11 @@ BasePayPalView.prototype.initialize = function () {
   var paypalType = isCredit ? 'paypalCredit' : 'paypal';
   var paypalConfiguration = this.model.merchantConfiguration[paypalType];
 
-  this.paypalConfiguration = assign({}, paypalConfiguration);
+  this.paypalConfiguration = assign({}, {
+    vault: {}
+  }, paypalConfiguration);
+  this.vaultConfig = this.paypalConfiguration.vault;
+  delete this.paypalConfiguration.vault;
 
   this.model.asyncDependencyStarting();
   asyncDependencyTimeoutHandler = setTimeout(function () {
@@ -41,10 +45,12 @@ BasePayPalView.prototype.initialize = function () {
     });
   }, ASYNC_DEPENDENCY_TIMEOUT);
 
-  return btPaypal.create({client: this.client}).then(function (paypalInstance) {
+  return btPaypal.create({
+    authorization: this.model.authorization
+  }).then(function (paypalInstance) {
     var checkoutJSConfiguration;
     var buttonSelector = '[data-braintree-id="paypal-button"]';
-    var environment = self.client.getConfiguration().gatewayConfiguration.environment === 'production' ? 'production' : 'sandbox';
+    var environment = self.model.environment === 'production' ? 'production' : 'sandbox';
     var locale = self.model.merchantConfiguration.locale;
 
     self.paypalInstance = paypalInstance;
@@ -58,8 +64,12 @@ BasePayPalView.prototype.initialize = function () {
         return paypalInstance.createPayment(self.paypalConfiguration).catch(reportError);
       },
       onAuthorize: function (data) {
+        var shouldVault = self._shouldVault();
+
+        data.vault = shouldVault;
+
         return paypalInstance.tokenizePayment(data).then(function (tokenizePayload) {
-          if (self.paypalConfiguration.flow === 'vault' && !self.model.isGuestCheckout) {
+          if (shouldVault) {
             tokenizePayload.vaulted = true;
           }
           self.model.addPaymentMethod(tokenizePayload);
@@ -117,21 +127,34 @@ BasePayPalView.prototype.requestPaymentMethod = function () {
 };
 
 BasePayPalView.prototype.updateConfiguration = function (key, value) {
+  if (key === 'vault') {
+    this.vaultConfig = value;
+
+    return;
+  }
+
   if (READ_ONLY_CONFIGURATION_OPTIONS.indexOf(key) === -1) {
     this.paypalConfiguration[key] = value;
   }
 };
 
-BasePayPalView.isEnabled = function (options) {
-  var gatewayConfiguration = options.client.getConfiguration().gatewayConfiguration;
-  var merchantPayPalConfig = options.merchantConfiguration.paypal || options.merchantConfiguration.paypalCredit;
+BasePayPalView.prototype._shouldVault = function () {
+  if (this.paypalConfiguration.flow !== 'vault') {
+    return false;
+  }
 
-  if (!gatewayConfiguration.paypalEnabled) {
-    return Promise.resolve(false);
+  if (this.vaultConfig.hasOwnProperty('autoVault')) {
+    return this.vaultConfig.autoVault;
   }
 
+  return this.model.vaultManagerConfig.autoVaultPaymentMethods;
+};
+
+BasePayPalView.isEnabled = function (options) {
+  var merchantPayPalConfig = options.merchantConfiguration.paypal || options.merchantConfiguration.paypalCredit;
+
   if (browserDetection.isIe9() || browserDetection.isIe10()) {
-    analytics.sendEvent(options.client, options.viewID + '.checkout.js-browser-not-supported');
+    analytics.sendEvent(options.viewID + '.checkout.js-browser-not-supported');
 
     return Promise.resolve(false);
   }
diff --git a/src/views/payment-sheet-views/card-view.js b/src/views/payment-sheet-views/card-view.js
index 18bf96d..bd9bec6 100644
--- a/src/views/payment-sheet-views/card-view.js
+++ b/src/views/payment-sheet-views/card-view.js
@@ -31,7 +31,6 @@ CardView.prototype.initialize = function () {
   hfOptions = this._generateHostedFieldsOptions();
 
   cardIcons.innerHTML = cardIconHTML;
-  this._hideUnsupportedCardIcons();
 
   this.hasCVV = hfOptions.fields.cvv;
   this.hasCardholderName = Boolean(this.merchantConfiguration.cardholderName);
@@ -78,18 +77,21 @@ CardView.prototype.initialize = function () {
     }
   }.bind(this));
 
-  if (!this.model.isGuestCheckout && this.merchantConfiguration.vault.allowVaultCardOverride === true) {
+  if (this.model.vaultManagerConfig.autoVaultPaymentMethods && this.merchantConfiguration.vault.allowAutoVaultOverride === true) {
     classList.remove(this.getElementById('save-card-field-group'), 'braintree-hidden');
   }
 
-  if (this.merchantConfiguration.vault.vaultCard === false) {
+  if (this.merchantConfiguration.vault.autoVault === false) {
     this.saveCardInput.checked = false;
   }
 
   this.model.asyncDependencyStarting();
 
+  // TODO we need some way to check if the merchant is actually enabled to process
+  // credit cards and then disable the card view if they are not
   return hostedFields.create(hfOptions).then(function (hostedFieldsInstance) {
     this.hostedFieldsInstance = hostedFieldsInstance;
+    this._showSupportedCardIcons();
     this.hostedFieldsInstance.on('blur', this._onBlurEvent.bind(this));
     this.hostedFieldsInstance.on('cardTypeChange', this._onCardTypeChangeEvent.bind(this));
     this.hostedFieldsInstance.on('focus', this._onFocusEvent.bind(this));
@@ -154,16 +156,18 @@ CardView.prototype._sendRequestableEvent = function () {
 };
 
 CardView.prototype._generateHostedFieldsOptions = function () {
-  var challenges = this.client.getConfiguration().gatewayConfiguration.challenges;
-  var hasCVVChallenge = challenges.indexOf('cvv') !== -1;
-  var hasPostalCodeChallenge = challenges.indexOf('postal_code') !== -1;
+  var shouldNotCollectCVV = this.merchantConfiguration.cvv && this.merchantConfiguration.cvv.collect === false;
+  var shouldNotCollectPostalCode = !(this.merchantConfiguration.postalCode && this.merchantConfiguration.postalCode.collect === true);
   var overrides = this.merchantConfiguration.overrides;
   var options = {
-    client: this.client,
+    authorization: this.model.authorization,
     fields: {
       number: {
         selector: this._generateFieldSelector('number'),
-        placeholder: generateCardNumberPlaceholder()
+        placeholder: generateCardNumberPlaceholder(),
+        // enforces card type as part of validity for hosted fields
+        // without providing any overrides
+        supportedCardBrands: {}
       },
       expirationDate: {
         selector: this._generateFieldSelector('expiration'),
@@ -204,11 +208,11 @@ CardView.prototype._generateHostedFieldsOptions = function () {
     }
   };
 
-  if (!hasCVVChallenge) {
+  if (shouldNotCollectCVV) {
     delete options.fields.cvv;
   }
 
-  if (!hasPostalCodeChallenge) {
+  if (shouldNotCollectPostalCode) {
     delete options.fields.postalCode;
   }
 
@@ -260,9 +264,8 @@ CardView.prototype._generateHostedFieldsOptions = function () {
 };
 
 CardView.prototype._validateForm = function (showFieldErrors) {
-  var card, cardType, cardTypeSupported, state;
+  var card, state;
   var isValid = true;
-  var supportedCardTypes = this.client.getConfiguration().gatewayConfiguration.creditCards.supportedCardTypes;
 
   if (!this.hostedFieldsInstance) {
     return false;
@@ -294,17 +297,11 @@ CardView.prototype._validateForm = function (showFieldErrors) {
     }
   }.bind(this));
 
-  if (state.fields.number.isValid) {
-    card = state.cards[0];
-    cardType = card && constants.configurationCardTypes[card.type];
-    cardTypeSupported = cardType && supportedCardTypes.indexOf(cardType) !== -1;
+  if (showFieldErrors && !state.fields.number.isValid) {
+    card = state.cards && state.cards[0];
 
-    if (!cardTypeSupported) {
-      isValid = false;
-
-      if (showFieldErrors) {
-        this.showFieldError('number', this.strings.unsupportedCardTypeError);
-      }
+    if (card && !card.supported) {
+      this.showFieldError('number', this.strings.unsupportedCardTypeError);
     }
   }
 
@@ -496,7 +493,11 @@ CardView.prototype.teardown = function () {
 };
 
 CardView.prototype._shouldVault = function () {
-  return !this.model.isGuestCheckout && this.saveCardInput.checked;
+  if (this.merchantConfiguration.vault.hasOwnProperty('autoVault')) {
+    return this.merchantConfiguration.vault.autoVault && this.saveCardInput.checked;
+  }
+
+  return this.model.vaultManagerConfig.autoVaultPaymentMethods && this.saveCardInput.checked;
 };
 
 CardView.prototype._generateFieldSelector = function (field) {
@@ -512,12 +513,14 @@ CardView.prototype._onBlurEvent = function (event) {
   if (shouldApplyFieldEmptyError(field)) {
     this.showFieldError(event.emittedBy, this.strings['fieldEmptyFor' + capitalize(event.emittedBy)]);
   } else if (!field.isEmpty && !field.isValid) {
-    this.showFieldError(event.emittedBy, this.strings['fieldInvalidFor' + capitalize(event.emittedBy)]);
-  } else if (event.emittedBy === 'number' && !this._isCardTypeSupported(event.cards[0].type)) {
-    this.showFieldError('number', this.strings.unsupportedCardTypeError);
+    if (event.emittedBy === 'number' && event.cards && event.cards[0] && !event.cards[0].supported) {
+      this.showFieldError('number', this.strings.unsupportedCardTypeError);
+    } else {
+      this.showFieldError(event.emittedBy, this.strings['fieldInvalidFor' + capitalize(event.emittedBy)]);
+    }
   }
 
-  setTimeout(function () {
+  window.setTimeout(function () {
     // when focusing on a field by clicking the label,
     // we need to wait a bit for the iframe to be
     // focused properly before applying validations
@@ -576,14 +579,8 @@ CardView.prototype._onNotEmptyEvent = function (event) {
 };
 
 CardView.prototype._onValidityChangeEvent = function (event) {
-  var isValid;
   var field = event.fields[event.emittedBy];
-
-  if (event.emittedBy === 'number' && event.cards[0]) {
-    isValid = field.isValid && this._isCardTypeSupported(event.cards[0].type);
-  } else {
-    isValid = field.isValid;
-  }
+  var isValid = field.isValid;
 
   classList.toggle(field.container, 'braintree-form__field--valid', isValid);
 
@@ -614,32 +611,26 @@ CardView.prototype.onSelection = function () {
   }
 };
 
-CardView.prototype._hideUnsupportedCardIcons = function () {
-  var supportedCardTypes = this.client.getConfiguration().gatewayConfiguration.creditCards.supportedCardTypes;
-
-  Object.keys(constants.configurationCardTypes).forEach(function (paymentMethodCardType) {
-    var cardIcon;
-    var configurationCardType = constants.configurationCardTypes[paymentMethodCardType];
-
-    if (supportedCardTypes.indexOf(configurationCardType) === -1) {
-      cardIcon = this.getElementById(paymentMethodCardType + '-card-icon');
-      classList.add(cardIcon, 'braintree-hidden');
-    }
-  }.bind(this));
-};
+CardView.prototype._showSupportedCardIcons = function () {
+  var self = this;
 
-CardView.prototype._isCardTypeSupported = function (cardType) {
-  var configurationCardType = constants.configurationCardTypes[cardType];
-  var supportedCardTypes = this.client.getConfiguration().gatewayConfiguration.creditCards.supportedCardTypes;
+  this.hostedFieldsInstance.getSupportedCardTypes().then(function (supportedCardTypes) {
+    Object.keys(constants.configurationCardTypes).forEach(function (paymentMethodCardType) {
+      var cardIcon;
+      var configurationKey = constants.configurationCardTypes[paymentMethodCardType];
 
-  return supportedCardTypes.indexOf(configurationCardType) !== -1;
+      if (supportedCardTypes.indexOf(paymentMethodCardType) > -1) {
+        cardIcon = self.getElementById(configurationKey + '-card-icon');
+        classList.remove(cardIcon, 'braintree-hidden');
+      }
+    });
+  });
 };
 
 CardView.isEnabled = function (options) {
-  var gatewayConfiguration = options.client.getConfiguration().gatewayConfiguration;
   var disabledByMerchant = options.merchantConfiguration.card === false;
 
-  return Promise.resolve(!disabledByMerchant && gatewayConfiguration.creditCards.supportedCardTypes.length > 0);
+  return Promise.resolve(!disabledByMerchant);
 };
 
 function isNormalFieldElement(element) {
diff --git a/src/views/payment-sheet-views/google-pay-view.js b/src/views/payment-sheet-views/google-pay-view.js
index 14f3caf..2a7682a 100644
--- a/src/views/payment-sheet-views/google-pay-view.js
+++ b/src/views/payment-sheet-views/google-pay-view.js
@@ -45,12 +45,13 @@ GooglePayView.prototype.initialize = function () {
   self.model.asyncDependencyStarting();
 
   return btGooglePay.create({
-    client: self.client,
+    authorization: this.model.authorization,
     googlePayVersion: googlePayVersion,
-    googleMerchantId: merchantId
+    googleMerchantId: merchantId,
+    useDeferredClient: true
   }).then(function (googlePayInstance) {
     self.googlePayInstance = googlePayInstance;
-    self.paymentsClient = createPaymentsClient(self.client);
+    self.paymentsClient = createPaymentsClient(self.model.environment);
   }).then(function () {
     var buttonContainer = self.getElementById('google-pay-button');
 
@@ -84,11 +85,11 @@ GooglePayView.prototype.tokenize = function () {
       console.error(err); // eslint-disable-line no-console
       reportedError = 'developerError';
     } else if (err.statusCode === 'CANCELED') {
-      analytics.sendEvent(self.client, 'googlepay.loadPaymentData.canceled');
+      analytics.sendEvent('googlepay.loadPaymentData.canceled');
 
       return;
     } else if (err.statusCode) {
-      analytics.sendEvent(self.client, 'googlepay.loadPaymentData.failed');
+      analytics.sendEvent('googlepay.loadPaymentData.failed');
     }
 
     self.model.reportError(reportedError);
@@ -100,9 +101,7 @@ GooglePayView.prototype.updateConfiguration = function (key, value) {
 };
 
 GooglePayView.isEnabled = function (options) {
-  var gatewayConfiguration = options.client.getConfiguration().gatewayConfiguration;
-
-  if (!(gatewayConfiguration.androidPay && Boolean(options.merchantConfiguration.googlePay))) {
+  if (!options.merchantConfiguration.googlePay) {
     return Promise.resolve(false);
   }
 
@@ -116,7 +115,7 @@ GooglePayView.isEnabled = function (options) {
 
     return Promise.resolve();
   }).then(function () {
-    var paymentsClient = createPaymentsClient(options.client);
+    var paymentsClient = createPaymentsClient(options.environment);
 
     return paymentsClient.isReadyToPay({
       allowedPaymentMethods: ['CARD', 'TOKENIZED_CARD']
@@ -126,9 +125,9 @@ GooglePayView.isEnabled = function (options) {
   });
 };
 
-function createPaymentsClient(client) {
+function createPaymentsClient(environment) {
   return new global.google.payments.api.PaymentsClient({
-    environment: client.getConfiguration().gatewayConfiguration.environment === 'production' ? 'PRODUCTION' : 'TEST'
+    environment: environment === 'production' ? 'PRODUCTION' : 'TEST'
   });
 }
 
diff --git a/src/views/payment-sheet-views/venmo-view.js b/src/views/payment-sheet-views/venmo-view.js
index 355707a..aff629f 100644
--- a/src/views/payment-sheet-views/venmo-view.js
+++ b/src/views/payment-sheet-views/venmo-view.js
@@ -17,7 +17,9 @@ VenmoView.ID = VenmoView.prototype.ID = paymentOptionIDs.venmo;
 
 VenmoView.prototype.initialize = function () {
   var self = this;
-  var venmoConfiguration = assign({}, self.model.merchantConfiguration.venmo, {client: this.client});
+  var venmoConfiguration = assign({}, self.model.merchantConfiguration.venmo, {
+    authorization: this.model.authorization
+  });
 
   self.model.asyncDependencyStarting();
 
@@ -74,8 +76,7 @@ VenmoView.prototype._isIgnorableError = function (error) {
 };
 
 VenmoView.isEnabled = function (options) {
-  var gatewayConfiguration = options.client.getConfiguration().gatewayConfiguration;
-  var venmoEnabled = gatewayConfiguration.payWithVenmo && Boolean(options.merchantConfiguration.venmo);
+  var venmoEnabled = Boolean(options.merchantConfiguration.venmo);
 
   if (!venmoEnabled) {
     return Promise.resolve(false);


Test patch:
diff --git a/test/app/index.html b/test/app/index.html
index 3205424..0a2a345 100644
--- a/test/app/index.html
+++ b/test/app/index.html
@@ -143,8 +143,7 @@
         totalPriceStatus: 'FINAL',
         totalPrice: '19.99'
       }
-    },
-    vaultManager: true
+    }
   };
   var dropinInstance;
   var requestPaymentMethodOptions = {};
diff --git a/test/helpers/fake.js b/test/helpers/fake.js
index ef28fb2..dbb3249 100644
--- a/test/helpers/fake.js
+++ b/test/helpers/fake.js
@@ -2,6 +2,7 @@
 var clientToken, clientTokenWithCustomerID, fakeBTInstances;
 var tokenizationKey = 'development_testing_merchant_id';
 var braintreeVersion = require('braintree-web').VERSION;
+var vaultManager = require('braintree-web/vault-manager');
 var DropinModel = require('../../src/dropin-model');
 
 function configuration() {
@@ -44,39 +45,30 @@ function configuration() {
   };
 }
 
-function getState() {
-  return {
-    cards: [{ type: 'visa' }],
-    fields: {
-      number: {
-        isValid: true
-      },
-      expirationDate: {
-        isValid: false
-      }
-    }
-  };
-}
-
 clientToken = configuration().gatewayConfiguration;
 clientToken.authorizationFingerprint = 'encoded_auth_fingerprint';
 clientToken = btoa(JSON.stringify(clientToken));
 
 clientTokenWithCustomerID = configuration().gatewayConfiguration;
 clientTokenWithCustomerID.authorizationFingerprint = 'encoded_auth_fingerprint&customer_id=abc123';
+clientTokenWithCustomerID.hasCustomer = true;
 clientTokenWithCustomerID = btoa(JSON.stringify(clientTokenWithCustomerID));
 
 fakeBTInstances = {
   dataCollector: {
     deviceData: 'device-data',
+    getDeviceData: jest.fn().mockResolvedValue('device-data'),
     teardown: function () {}
   },
   hostedFields: {
-    getState: getState,
-    on: function () {},
-    setAttribute: function () {},
-    setMessage: function () {},
-    tokenize: function () {}
+    clear: jest.fn(),
+    getState: jest.fn(),
+    on: jest.fn(),
+    getSupportedCardTypes: jest.fn(),
+    setAttribute: jest.fn(),
+    removeAttribute: jest.fn(),
+    setMessage: jest.fn(),
+    tokenize: jest.fn()
   },
   paypal: {
     createPayment: function () {},
@@ -86,6 +78,10 @@ fakeBTInstances = {
     verifyCard: function () {},
     cancelVerifyCard: function () {},
     teardown: function () {}
+  },
+  vaultManager: {
+    fetchPaymentMethods: jest.fn(),
+    deletePaymentMethod: jest.fn()
   }
 };
 
@@ -108,13 +104,13 @@ function model(options) {
   modelInstance = new DropinModel(options);
 
   jest.spyOn(modelInstance, 'getVaultedPaymentMethods').mockResolvedValue([]);
+  jest.spyOn(vaultManager, 'create').mockResolvedValue(fakeBTInstances.vaultManager);
 
   return modelInstance;
 }
 
 function modelOptions() {
   return {
-    client: client(),
     componentID: 'foo123',
     merchantConfiguration: {
       authorization: tokenizationKey
@@ -132,6 +128,7 @@ module.exports = {
   hostedFieldsInstance: fakeBTInstances.hostedFields,
   paypalInstance: fakeBTInstances.paypal,
   threeDSecureInstance: fakeBTInstances.threeDSecure,
+  vaultManagerInstance: fakeBTInstances.vaultManager,
   modelOptions: modelOptions,
   tokenizationKey: tokenizationKey
 };
diff --git a/test/unit/dropin-model.js b/test/unit/dropin-model.js
index 4306b7a..22bf2d4 100644
--- a/test/unit/dropin-model.js
+++ b/test/unit/dropin-model.js
@@ -1,6 +1,6 @@
+jest.mock('../../src/lib/analytics');
 
 const vaultManager = require('braintree-web/vault-manager');
-const analytics = require('../../src/lib/analytics');
 const DropinModel = require('../../src/dropin-model');
 const ApplePayView = require('../../src/views/payment-sheet-views/apple-pay-view');
 const CardView = require('../../src/views/payment-sheet-views/card-view');
@@ -10,6 +10,7 @@ const PayPalCreditView = require('../../src/views/payment-sheet-views/paypal-cre
 const VenmoView = require('../../src/views/payment-sheet-views/venmo-view');
 const EventEmitter = require('@braintree/event-emitter');
 const isHTTPS = require('../../src/lib/is-https');
+const analytics = require('../../src/lib/analytics');
 const fake = require('../helpers/fake');
 const throwIfResolves = require('../helpers/throw-if-resolves');
 
@@ -23,13 +24,8 @@ describe('DropinModel', () => {
   beforeEach(() => {
     testContext.configuration = fake.configuration();
 
-    testContext.vaultManager = {
-      fetchPaymentMethods: jest.fn().mockResolvedValue([]),
-      deletePaymentMethod: jest.fn().mockResolvedValue()
-    };
-    jest.spyOn(vaultManager, 'create').mockResolvedValue(testContext.vaultManager);
+    jest.spyOn(vaultManager, 'create').mockResolvedValue(fake.vaultManagerInstance);
     testContext.modelOptions = {
-      client: fake.client(testContext.configuration),
       componentID: 'foo123',
       merchantConfiguration: {
         authorization: fake.clientToken,
@@ -38,7 +34,6 @@ describe('DropinModel', () => {
       },
       paymentMethods: []
     };
-    jest.spyOn(analytics, 'sendEvent').mockImplementation();
     jest.spyOn(console, 'error').mockImplementation();
 
     jest.spyOn(isHTTPS, 'isHTTPS').mockReturnValue(true);
@@ -51,6 +46,9 @@ describe('DropinModel', () => {
     jest.spyOn(PayPalView, 'isEnabled').mockResolvedValue(true);
     jest.spyOn(PayPalCreditView, 'isEnabled').mockResolvedValue(true);
     jest.spyOn(VenmoView, 'isEnabled').mockResolvedValue(true);
+
+    fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([]);
+    fake.vaultManagerInstance.deletePaymentMethod.mockResolvedValue();
   });
 
   describe('Constructor', () => {
@@ -69,72 +67,147 @@ describe('DropinModel', () => {
 
       expect(model.merchantConfiguration).toBe(testContext.modelOptions.merchantConfiguration);
     });
+  });
 
-    describe('isGuestCheckout', () => {
-      test('is true when given a tokenization key', () => {
-        let model;
+  describe('confirmDropinReady', () => {
+    test('sets _setupComplete to true', () => {
+      const model = new DropinModel(testContext.modelOptions);
 
-        testContext.configuration.authorization = fake.tokenizationKey;
-        testContext.configuration.authorizationType = 'TOKENIZATION_KEY';
+      expect(model._setupComplete).toBe(false);
 
-        model = new DropinModel(testContext.modelOptions);
+      model.confirmDropinReady();
 
-        expect(model.isGuestCheckout).toBe(true);
-      });
+      expect(model._setupComplete).toBe(true);
+    });
+  });
 
-      test('is true when given a client token without a customer ID', () => {
-        let model;
+  describe('initialize', () => {
+    test('sends web.dropin.started.tokenization-key event when using a tokenization key', async () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.tokenizationKey;
+      const model = new DropinModel(testContext.modelOptions);
 
-        testContext.configuration.authorization = fake.clientToken;
-        testContext.configuration.authorizationType = 'CLIENT_TOKEN';
+      await model.initialize();
 
-        model = new DropinModel(testContext.modelOptions);
+      expect(analytics.sendEvent).toBeCalledWith('started.tokenization-key');
+    });
+
+    test('sends web.dropin.started.client-token event when using a client token', async () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientToken;
+      const model = new DropinModel(testContext.modelOptions);
+
+      await model.initialize();
+
+      expect(analytics.sendEvent).toBeCalledWith('started.client-token');
+    });
+
+    test('sets vault manager config with defaults for client tokens with customer ids if not provided', async () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
+      const model = new DropinModel(testContext.modelOptions);
 
-        expect(model.isGuestCheckout).toBe(true);
+      await model.initialize();
+
+      expect(model.vaultManagerConfig).toEqual({
+        autoVaultPaymentMethods: true,
+        presentVaultedPaymentMethods: true,
+        preselectVaultedPaymentMethod: true,
+        allowCustomerToDeletePaymentMethods: false
       });
+    });
 
-      test('is false when given a client token with a customer ID', () => {
-        let model;
+    test('sets vault manager config with defaults for client tokens without customer ids if not provided', async () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientToken;
+      const model = new DropinModel(testContext.modelOptions);
 
-        testContext.configuration.authorization = fake.clientTokenWithCustomerID;
-        testContext.configuration.authorizationType = 'CLIENT_TOKEN';
+      await model.initialize();
 
-        model = new DropinModel(testContext.modelOptions);
+      expect(model.vaultManagerConfig).toEqual({
+        autoVaultPaymentMethods: false,
+        presentVaultedPaymentMethods: false,
+        preselectVaultedPaymentMethod: false,
+        allowCustomerToDeletePaymentMethods: false
+      });
+    });
+
+    test('sets vault manager config with defaults for tokenization keys if not provided', async () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.tokenizationKey;
+      const model = new DropinModel(testContext.modelOptions);
+
+      await model.initialize();
 
-        expect(model.isGuestCheckout).toBe(false);
+      expect(model.vaultManagerConfig).toEqual({
+        autoVaultPaymentMethods: false,
+        presentVaultedPaymentMethods: false,
+        preselectVaultedPaymentMethod: false,
+        allowCustomerToDeletePaymentMethods: false
       });
     });
-  });
 
-  describe('confirmDropinReady', () => {
-    test('sets _setupComplete to true', () => {
+    test('rejects with an error when passing a vault manager option with a tokeniation key', async () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.tokenizationKey;
+      testContext.modelOptions.merchantConfiguration.vaultManager = {
+        autoVaultPaymentMethods: true
+      };
       const model = new DropinModel(testContext.modelOptions);
 
-      expect(model._setupComplete).toBe(false);
+      await expect(model.initialize()).rejects.toThrow('vaultManager cannot be used with tokenization keys.');
+    });
 
-      model.confirmDropinReady();
+    test('overrides the vault manager config if provided', async () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
+      testContext.modelOptions.merchantConfiguration.vaultManager = {
+        autoVaultPaymentMethods: false,
+        presentVaultedPaymentMethods: false,
+        preselectVaultedPaymentMethod: false,
+        allowCustomerToDeletePaymentMethods: true
+      };
+      const model = new DropinModel(testContext.modelOptions);
 
-      expect(model._setupComplete).toBe(true);
+      await model.initialize();
+
+      expect(model.vaultManagerConfig).toEqual({
+        autoVaultPaymentMethods: false,
+        presentVaultedPaymentMethods: false,
+        preselectVaultedPaymentMethod: false,
+        allowCustomerToDeletePaymentMethods: true
+      });
+    });
+
+    test('provides vault manager defaults when only some options are provided', async () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
+      testContext.modelOptions.merchantConfiguration.vaultManager = {
+        autoVaultPaymentMethods: false
+      };
+      const model = new DropinModel(testContext.modelOptions);
+
+      await model.initialize();
+
+      expect(model.vaultManagerConfig).toEqual({
+        autoVaultPaymentMethods: false,
+        presentVaultedPaymentMethods: true,
+        preselectVaultedPaymentMethod: true,
+        allowCustomerToDeletePaymentMethods: false
+      });
     });
-  });
 
-  describe('initialize', () => {
     test('creates a vault manager', () => {
       const model = new DropinModel(testContext.modelOptions);
 
       return model.initialize().then(() => {
         expect(vaultManager.create).toBeCalledTimes(1);
         expect(vaultManager.create).toBeCalledWith({
-          client: testContext.modelOptions.client
+          authorization: fake.clientToken
         });
       });
     });
 
     test('sets existing payment methods as _paymentMethods', () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
+
       const model = new DropinModel(testContext.modelOptions);
 
-      model.isGuestCheckout = false;
-      testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([{ type: 'CreditCard', details: { lastTwo: '11' }}]);
+      fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([
+        { type: 'CreditCard', details: { lastTwo: '11' }}
+      ]);
 
       return model.initialize().then(() => {
         expect(model._paymentMethods).toEqual([{ type: 'CreditCard', details: { lastTwo: '11' }, vaulted: true }]);
@@ -142,6 +215,8 @@ describe('DropinModel', () => {
     });
 
     test('_paymentMethods is empty if no existing payment methods', () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
+
       const model = new DropinModel(testContext.modelOptions);
 
       return model.initialize().then(() => {
@@ -150,10 +225,11 @@ describe('DropinModel', () => {
     });
 
     test('ignores valid, but disabled payment methods', () => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
+
       const model = new DropinModel(testContext.modelOptions);
 
-      model.isGuestCheckout = false;
-      testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([
+      fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([
         { type: 'CreditCard', details: { lastTwo: '11' }},
         { type: 'PayPalAccount', details: { email: 'wow@example.com' }}
       ]);
@@ -170,10 +246,11 @@ describe('DropinModel', () => {
     test(
       'ignores payment methods that have errored when calling isEnabled',
       () => {
+        testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
+
         const model = new DropinModel(testContext.modelOptions);
 
-        model.isGuestCheckout = false;
-        testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([
+        fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([
           { type: 'CreditCard', details: { lastTwo: '11' }},
           { type: 'PayPalAccount', details: { email: 'wow@example.com' }}
         ]);
@@ -193,7 +270,7 @@ describe('DropinModel', () => {
       const model = new DropinModel(testContext.modelOptions);
       const error = new Error('fail');
 
-      testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([
+      fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([
         { type: 'CreditCard', details: { lastTwo: '11' }},
         { type: 'PayPalAccount', details: { email: 'wow@example.com' }}
       ]);
@@ -297,32 +374,32 @@ describe('DropinModel', () => {
         return model.initialize().then(() => {
           expect(ApplePayView.isEnabled).toBeCalledTimes(1);
           expect(ApplePayView.isEnabled).toBeCalledWith({
-            client: testContext.modelOptions.client,
+            environment: 'development',
             merchantConfiguration: testContext.modelOptions.merchantConfiguration
           });
           expect(CardView.isEnabled).toBeCalledTimes(1);
           expect(CardView.isEnabled).toBeCalledWith({
-            client: testContext.modelOptions.client,
+            environment: 'development',
             merchantConfiguration: testContext.modelOptions.merchantConfiguration
           });
           expect(GooglePayView.isEnabled).toBeCalledTimes(1);
           expect(GooglePayView.isEnabled).toBeCalledWith({
-            client: testContext.modelOptions.client,
+            environment: 'development',
             merchantConfiguration: testContext.modelOptions.merchantConfiguration
           });
           expect(PayPalView.isEnabled).toBeCalledTimes(1);
           expect(PayPalView.isEnabled).toBeCalledWith({
-            client: testContext.modelOptions.client,
+            environment: 'development',
             merchantConfiguration: testContext.modelOptions.merchantConfiguration
           });
           expect(PayPalCreditView.isEnabled).toBeCalledTimes(1);
           expect(PayPalCreditView.isEnabled).toBeCalledWith({
-            client: testContext.modelOptions.client,
+            environment: 'development',
             merchantConfiguration: testContext.modelOptions.merchantConfiguration
           });
           expect(VenmoView.isEnabled).toBeCalledTimes(1);
           expect(VenmoView.isEnabled).toBeCalledWith({
-            client: testContext.modelOptions.client,
+            environment: 'development',
             merchantConfiguration: testContext.modelOptions.merchantConfiguration
           });
         });
@@ -456,6 +533,52 @@ describe('DropinModel', () => {
     });
   });
 
+  describe('removeUnvaultedPaymentMethods', () => {
+    test('removes all unvaulted payment methods', () => {
+      const model = new DropinModel(testContext.modelOptions);
+
+      model._paymentMethods = [];
+
+      const unvaultedPaymentMethod = { id: 'unvaulted-1' };
+      const vaultedPaymentMethod = { id: 'vaulted', vaulted: true };
+      const otherUnvaultedPaymentMethod = { id: 'unvaulted-2' };
+
+      model.addPaymentMethod(unvaultedPaymentMethod);
+      model.addPaymentMethod(vaultedPaymentMethod);
+      model.addPaymentMethod(otherUnvaultedPaymentMethod);
+
+      model.removeUnvaultedPaymentMethods();
+
+      expect(model._paymentMethods).toEqual([{
+        id: 'vaulted', vaulted: true
+      }]);
+    });
+
+    test('removes all unvaulted payment methods that match specified filter', () => {
+      const model = new DropinModel(testContext.modelOptions);
+
+      model._paymentMethods = [];
+
+      const unvaultedPaymentMethod = { id: 'unvaulted-1' };
+      const vaultedPaymentMethod = { id: 'vaulted', vaulted: true };
+      const otherUnvaultedPaymentMethod = { id: 'unvaulted-2' };
+
+      model.addPaymentMethod(unvaultedPaymentMethod);
+      model.addPaymentMethod(vaultedPaymentMethod);
+      model.addPaymentMethod(otherUnvaultedPaymentMethod);
+
+      model.removeUnvaultedPaymentMethods((paymentMethod) => {
+        return paymentMethod.id === 'unvaulted-1';
+      });
+
+      expect(model._paymentMethods).toEqual([{
+        id: 'vaulted', vaulted: true
+      }, {
+        id: 'unvaulted-2'
+      }]);
+    });
+  });
+
   describe('refreshPaymentMethods', () => {
     beforeEach(() => {
       testContext.model = new DropinModel(testContext.modelOptions);
@@ -734,7 +857,7 @@ describe('DropinModel', () => {
 
   describe('isPaymentMethodRequestable', () => {
     test(
-      'returns false initially if no payment methods are passed in',
+      'returns false initially if no payment methods are available',
       () => {
         const model = new DropinModel(testContext.modelOptions);
 
@@ -747,10 +870,10 @@ describe('DropinModel', () => {
     test(
       'returns true initially if customer has saved payment methods',
       () => {
+        testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
         const model = new DropinModel(testContext.modelOptions);
 
-        model.isGuestCheckout = false;
-        testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([{ type: 'CreditCard', details: { lastTwo: '11' }}]);
+        fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([{ type: 'CreditCard', details: { lastTwo: '11' }}]);
 
         return model.initialize().then(() => {
           expect(model.isPaymentMethodRequestable()).toBe(true);
@@ -1061,9 +1184,9 @@ describe('DropinModel', () => {
       testContext.model = new DropinModel(testContext.modelOptions);
 
       testContext.model._paymentMethodWaitingToBeDeleted = {
-        nonce: 'a-nonce'
+        nonce: 'a-nonce',
+        vaulted: true
       };
-      testContext.model.isGuestCheckout = false;
       jest.spyOn(testContext.model, '_emit').mockImplementation();
 
       return testContext.model.initialize();
@@ -1083,18 +1206,18 @@ describe('DropinModel', () => {
 
     test('uses vault manager to delete payment method', () => {
       return testContext.model.deleteVaultedPaymentMethod().then(() => {
-        expect(testContext.vaultManager.deletePaymentMethod).toBeCalledTimes(1);
-        expect(testContext.vaultManager.deletePaymentMethod).toBeCalledWith('a-nonce');
+        expect(fake.vaultManagerInstance.deletePaymentMethod).toBeCalledTimes(1);
+        expect(fake.vaultManagerInstance.deletePaymentMethod).toBeCalledWith('a-nonce');
       });
     });
 
     test(
-      'does not vault manager to delete payment method if in guest checkout mode',
+      'does not vault manager to delete payment method if payment method is not vaulted',
       () => {
-        testContext.model.isGuestCheckout = true;
+        delete testContext.model._paymentMethodWaitingToBeDeleted.vaulted;
 
         return testContext.model.deleteVaultedPaymentMethod().then(() => {
-          expect(testContext.vaultManager.deletePaymentMethod).not.toBeCalled();
+          expect(fake.vaultManagerInstance.deletePaymentMethod).not.toBeCalled();
         });
       }
     );
@@ -1102,7 +1225,7 @@ describe('DropinModel', () => {
     test(
       'emits finishVaultedPaymentMethodDeletion event when deletion is succesful',
       () => {
-        testContext.vaultManager.deletePaymentMethod.mockResolvedValue();
+        fake.vaultManagerInstance.deletePaymentMethod.mockResolvedValue();
 
         return testContext.model.deleteVaultedPaymentMethod().then(() => {
           expect(testContext.model._emit).toBeCalledWith('finishVaultedPaymentMethodDeletion', undefined); // eslint-disable-line no-undefined
@@ -1115,7 +1238,7 @@ describe('DropinModel', () => {
       () => {
         const error = new Error('aaaaaaah!');
 
-        testContext.vaultManager.deletePaymentMethod.mockRejectedValue(error);
+        fake.vaultManagerInstance.deletePaymentMethod.mockRejectedValue(error);
 
         return testContext.model.deleteVaultedPaymentMethod().then(() => {
           expect(testContext.model._emit).toBeCalledWith('finishVaultedPaymentMethodDeletion', expect.any(Error));
@@ -1161,21 +1284,19 @@ describe('DropinModel', () => {
 
   describe('getVaultedPaymentMethods', () => {
     beforeEach(() => {
+      testContext.modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
       testContext.model = new DropinModel(testContext.modelOptions);
 
-      return testContext.model.initialize().then(() => {
-        testContext.model.isGuestCheckout = false;
-      });
+      return testContext.model.initialize();
     });
 
     test(
       'resolves with payment methods as empty array when vault manager errors',
       () => {
-        testContext.model.isGuestCheckout = false;
-        testContext.vaultManager.fetchPaymentMethods.mockRejectedValue(new Error('error'));
+        fake.vaultManagerInstance.fetchPaymentMethods.mockRejectedValue(new Error('error'));
 
         return testContext.model.getVaultedPaymentMethods().then(paymentMethods => {
-          expect(testContext.vaultManager.fetchPaymentMethods).toBeCalledWith({
+          expect(fake.vaultManagerInstance.fetchPaymentMethods).toBeCalledWith({
             defaultFirst: true
           });
           expect(paymentMethods).toEqual([]);
@@ -1184,9 +1305,9 @@ describe('DropinModel', () => {
     );
 
     test(
-      'resolves with payment methods as empty array when in guest checkout',
+      'resolves with payment methods as empty array when presentVaultedPaymentMethods is false',
       () => {
-        testContext.model.isGuestCheckout = true;
+        testContext.model.vaultManagerConfig.presentVaultedPaymentMethods = false;
 
         return testContext.model.getVaultedPaymentMethods().then(paymentMethods => {
           expect(paymentMethods).toEqual([]);
@@ -1197,8 +1318,7 @@ describe('DropinModel', () => {
     test(
       'resolves with payment methods from vault manager when not in guest checkout',
       () => {
-        testContext.model.isGuestCheckout = false;
-        testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([{
+        fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([{
           nonce: '1-nonce',
           type: 'CreditCard'
         }, {
@@ -1207,7 +1327,7 @@ describe('DropinModel', () => {
         }]);
 
         return testContext.model.getVaultedPaymentMethods().then(paymentMethods => {
-          expect(testContext.vaultManager.fetchPaymentMethods).toBeCalledWith({
+          expect(fake.vaultManagerInstance.fetchPaymentMethods).toBeCalledWith({
             defaultFirst: true
           });
           expect(paymentMethods).toEqual([{
@@ -1224,8 +1344,7 @@ describe('DropinModel', () => {
     );
 
     test('only resolves supported payment method types', () => {
-      testContext.model.isGuestCheckout = false;
-      testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([{
+      fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([{
         nonce: '1-nonce',
         type: 'CreditCard'
       }, {
@@ -1243,8 +1362,7 @@ describe('DropinModel', () => {
     });
 
     test('includes vaulted property on payment method objects', () => {
-      testContext.model.isGuestCheckout = false;
-      testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([{
+      fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([{
         nonce: '1-nonce',
         type: 'CreditCard'
       }, {
@@ -1256,7 +1374,7 @@ describe('DropinModel', () => {
       }]);
 
       return testContext.model.getVaultedPaymentMethods().then(paymentMethods => {
-        expect(testContext.vaultManager.fetchPaymentMethods).toBeCalledWith({
+        expect(fake.vaultManagerInstance.fetchPaymentMethods).toBeCalledWith({
           defaultFirst: true
         });
         expect(paymentMethods[0].vaulted).toBe(true);
@@ -1266,7 +1384,7 @@ describe('DropinModel', () => {
     });
 
     test('ignores invalid payment methods', () => {
-      testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([
+      fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([
         { type: 'CreditCard', details: { lastTwo: '11' }},
         { type: 'PayPalAccount', details: { email: 'wow@example.com' }},
         { type: 'InvalidMethod', details: {}},
@@ -1285,7 +1403,7 @@ describe('DropinModel', () => {
     test(
       'ignores vaulted payment methods that cannot be used client side',
       () => {
-        testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([
+        fake.vaultManagerInstance.fetchPaymentMethods.mockResolvedValue([
           { type: 'CreditCard', details: { lastTwo: '11' }},
           { type: 'PayPalAccount', details: { email: 'wow@example.com' }},
           { type: 'ApplePayCard', details: {}},
diff --git a/test/unit/dropin.js b/test/unit/dropin.js
index 6687696..d2018db 100644
--- a/test/unit/dropin.js
+++ b/test/unit/dropin.js
@@ -1,3 +1,4 @@
+jest.mock('../../src/lib/analytics');
 
 const Dropin = require('../../src/dropin/');
 const DropinModel = require('../../src/dropin-model');
@@ -30,7 +31,6 @@ describe('Dropin', () => {
 
   beforeEach(() => {
     testContext = {};
-    testContext.client = fake.client();
     testContext.vaultManager = {
       fetchPaymentMethods: jest.fn().mockResolvedValue([])
     };
@@ -41,18 +41,18 @@ describe('Dropin', () => {
     document.body.appendChild(testContext.container);
 
     testContext.dropinOptions = {
-      client: testContext.client,
       merchantConfiguration: {
         container: '#foo',
         authorization: fake.tokenizationKey
       }
     };
 
-    jest.spyOn(analytics, 'sendEvent').mockImplementation();
     jest.spyOn(CardView.prototype, 'getPaymentMethod').mockImplementation();
     jest.spyOn(hostedFields, 'create').mockResolvedValue(fake.hostedFieldsInstance);
     jest.spyOn(paypalCheckout, 'create').mockResolvedValue(fake.paypalInstance);
     jest.spyOn(threeDSecure, 'create').mockResolvedValue(fake.threeDSecureInstance);
+
+    fake.hostedFieldsInstance.getSupportedCardTypes.mockResolvedValue([]);
   });
 
   afterEach(() => {
@@ -102,7 +102,7 @@ describe('Dropin', () => {
 
       instance._initialize(err => {
         expect(err.message).toBe('options.container is required.');
-        expect(analytics.sendEvent).toBeCalledWith(instance._client, 'configuration-error');
+        expect(analytics.sendEvent).toBeCalledWith('configuration-error');
         done();
       });
     });
@@ -116,7 +116,7 @@ describe('Dropin', () => {
 
       instance._initialize(err => {
         expect(err.message).toBe('Must only have one options.selector or options.container.');
-        expect(analytics.sendEvent).toBeCalledWith(instance._client, 'configuration-error');
+        expect(analytics.sendEvent).toBeCalledWith('configuration-error');
         done();
       });
     });
@@ -137,7 +137,7 @@ describe('Dropin', () => {
         expect(err).toBeInstanceOf(Error);
         expect(err.message).toBe('All payment options failed to load.');
         expect(instance._dropinWrapper.innerHTML).toBe('');
-        expect(analytics.sendEvent).toBeCalledWith(instance._client, 'load-error');
+        expect(analytics.sendEvent).toBeCalledWith('load-error');
         done();
       });
     });
@@ -206,7 +206,7 @@ describe('Dropin', () => {
         instance._initialize(err => {
           expect(err).toBeInstanceOf(Error);
           expect(err.message).toBe('options.selector or options.container must reference a valid DOM node.');
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'configuration-error');
+          expect(analytics.sendEvent).toBeCalledWith('configuration-error');
           done();
         });
       }
@@ -225,7 +225,7 @@ describe('Dropin', () => {
         instance._initialize(err => {
           expect(err).toBeInstanceOf(Error);
           expect(err.message).toBe('options.selector or options.container must reference an empty DOM node.');
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'configuration-error');
+          expect(analytics.sendEvent).toBeCalledWith('configuration-error');
           done();
         });
       }
@@ -246,7 +246,7 @@ describe('Dropin', () => {
         instance._initialize(err => {
           expect(err).toBeInstanceOf(Error);
           expect(err.message).toBe('options.selector or options.container must reference an empty DOM node.');
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'configuration-error');
+          expect(analytics.sendEvent).toBeCalledWith('configuration-error');
           done();
         });
       }
@@ -265,7 +265,7 @@ describe('Dropin', () => {
         instance._initialize(err => {
           expect(err).toBeInstanceOf(Error);
           expect(err.message).toBe('options.selector or options.container must reference a valid DOM node.');
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'configuration-error');
+          expect(analytics.sendEvent).toBeCalledWith('configuration-error');
           done();
         });
       }
@@ -360,21 +360,16 @@ describe('Dropin', () => {
     test('requests payment methods if a customerId is provided', done => {
       let instance;
 
-      testContext.client.getConfiguration.mockReturnValue({
-        authorization: fake.clientTokenWithCustomerID,
-        authorizationType: 'CLIENT_TOKEN',
-        gatewayConfiguration: fake.configuration().gatewayConfiguration
-      });
-
       testContext.vaultManager.fetchPaymentMethods.mockResolvedValue([]);
 
+      testContext.dropinOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
       instance = new Dropin(testContext.dropinOptions);
 
       instance._initialize(() => {
         try {
           expect(vaultManager.create).toBeCalledTimes(1);
           expect(vaultManager.create).toBeCalledWith({
-            client: testContext.client
+            authorization: fake.clientTokenWithCustomerID
           });
           expect(testContext.vaultManager.fetchPaymentMethods).toBeCalledTimes(1);
           expect(testContext.vaultManager.fetchPaymentMethods).toBeCalledWith(expect.objectContaining({
@@ -388,40 +383,10 @@ describe('Dropin', () => {
       });
     });
 
-    test(
-      'does not fail if there is an error getting existing payment methods',
-      done => {
-        let instance;
-
-        testContext.client.getConfiguration.mockReturnValue({
-          authorization: fake.clientTokenWithCustomerID,
-          authorizationType: 'CLIENT_TOKEN',
-          gatewayConfiguration: fake.configuration().gatewayConfiguration
-        });
-        testContext.client.request.mockRejectedValue(new Error('This failed'));
-
-        instance = new Dropin(testContext.dropinOptions);
-
-        instance._initialize(() => {
-          expect(hostedFields.create).toBeCalled();
-          expect(instance._model.getPaymentMethods()).toHaveLength(0);
-
-          done();
-        });
-      }
-    );
-
-    test('creates a MainView a customerId exists', done => {
+    test('creates a MainView when a customerId exists', done => {
       let instance;
-      const paymentMethodsPayload = { paymentMethods: []};
-
-      testContext.client.getConfiguration.mockReturnValue({
-        authorization: fake.clientTokenWithCustomerID,
-        authorizationType: 'CLIENT_TOKEN',
-        gatewayConfiguration: fake.configuration().gatewayConfiguration
-      });
-      testContext.client.request.mockResolvedValue(paymentMethodsPayload);
 
+      testContext.dropinOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
       instance = new Dropin(testContext.dropinOptions);
 
       instance._initialize(() => {
@@ -432,6 +397,8 @@ describe('Dropin', () => {
     });
 
     test('creates a MainView a customerId does not exist', done => {
+      testContext.dropinOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
+
       const instance = new Dropin(testContext.dropinOptions);
 
       instance._initialize(() => {
@@ -516,8 +483,7 @@ describe('Dropin', () => {
         jest.spyOn(DropinModel.prototype, 'asyncDependencyReady').mockImplementation();
 
         instance._initialize(() => {
-          expect(analytics.sendEvent).toBeCalledTimes(1);
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'appeared');
+          expect(analytics.sendEvent).toBeCalledWith('appeared');
           done();
         });
 
@@ -539,8 +505,8 @@ describe('Dropin', () => {
         ]);
 
         instance._initialize(() => {
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'vaulted-card.appear');
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'vaulted-paypal.appear');
+          expect(analytics.sendEvent).toBeCalledWith('vaulted-card.appear');
+          expect(analytics.sendEvent).toBeCalledWith('vaulted-paypal.appear');
           done();
         });
       }
@@ -559,8 +525,8 @@ describe('Dropin', () => {
         ]);
 
         instance._initialize(() => {
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'vaulted-card.appear');
-          expect(analytics.sendEvent).toBeCalledWith(instance._client, 'vaulted-paypal.appear');
+          expect(analytics.sendEvent).toBeCalledWith('vaulted-card.appear');
+          expect(analytics.sendEvent).toBeCalledWith('vaulted-paypal.appear');
           done();
         });
       }
@@ -579,8 +545,8 @@ describe('Dropin', () => {
         jest.spyOn(DropinModel.prototype, 'asyncDependencyReady').mockImplementation();
 
         instance._initialize(() => {
-          expect(analytics.sendEvent).not.toBeCalledWith(instance._client, 'vaulted-card.appear');
-          expect(analytics.sendEvent).not.toBeCalledWith(instance._client, 'vaulted-paypal.appear');
+          expect(analytics.sendEvent).not.toBeCalledWith('vaulted-card.appear');
+          expect(analytics.sendEvent).not.toBeCalledWith('vaulted-paypal.appear');
 
           done();
         });
@@ -610,8 +576,8 @@ describe('Dropin', () => {
         jest.spyOn(DropinModel.prototype, 'asyncDependencyReady').mockImplementation();
 
         instance._initialize(() => {
-          expect(analytics.sendEvent).not.toBeCalledWith(instance._client, 'vaulted-card.appear');
-          expect(analytics.sendEvent).not.toBeCalledWith(instance._client, 'vaulted-paypal.appear');
+          expect(analytics.sendEvent).not.toBeCalledWith('vaulted-card.appear');
+          expect(analytics.sendEvent).not.toBeCalledWith('vaulted-paypal.appear');
 
           done();
         });
@@ -639,7 +605,7 @@ describe('Dropin', () => {
         ]);
 
         instance._initialize(() => {
-          expect(analytics.sendEvent).not.toBeCalledWith(instance._client, 'vaulted-card.appear');
+          expect(analytics.sendEvent).not.toBeCalledWith('vaulted-card.appear');
           done();
         });
       }
@@ -838,7 +804,6 @@ describe('Dropin', () => {
     beforeEach(() => {
       testContext.model = fake.model({
         componentID: 'foo',
-        client: testContext.client,
         merchantConfiguration: {
           container: '#foo',
           authorization: fake.tokenizationKey
@@ -850,7 +815,7 @@ describe('Dropin', () => {
 
     test('sets up datacollector', () => {
       Dropin.prototype._setUpDataCollector.call({
-        _client: testContext.client,
+        _authorization: 'fake-auth',
         _model: testContext.model,
         _strings: {},
         _merchantConfiguration: {
@@ -873,7 +838,7 @@ describe('Dropin', () => {
         DataCollector.prototype.initialize.mockRejectedValue(error);
 
         Dropin.prototype._setUpDataCollector.call({
-          _client: testContext.client,
+          _authorization: 'fake-auth',
           _model: testContext.model,
           _merchantConfiguration: {
             threeDSecure: {
@@ -895,7 +860,7 @@ describe('Dropin', () => {
       jest.spyOn(DropinModel.prototype, 'asyncDependencyStarting');
 
       Dropin.prototype._setUpDataCollector.call({
-        _client: testContext.client,
+        _authorization: 'fake-auth',
         _merchantConfiguration: {
           threeDSecure: {
             foo: 'bar'
@@ -912,7 +877,6 @@ describe('Dropin', () => {
     beforeEach(() => {
       testContext.model = fake.model({
         componentID: 'foo',
-        client: testContext.client,
         merchantConfiguration: {
           container: '#foo',
           authorization: fake.tokenizationKey
@@ -924,7 +888,7 @@ describe('Dropin', () => {
 
     test('sets up 3ds', () => {
       Dropin.prototype._setUpThreeDSecure.call({
-        _client: testContext.client,
+        _authorization: 'fake-auth',
         _model: testContext.model,
         _merchantConfiguration: {
           threeDSecure: {
@@ -944,7 +908,7 @@ describe('Dropin', () => {
       ThreeDSecure.prototype.initialize.mockRejectedValue(error);
 
       Dropin.prototype._setUpThreeDSecure.call({
-        _client: testContext.client,
+        _authorization: 'fake-auth',
         _model: testContext.model,
         _merchantConfiguration: {
           threeDSecure: {
@@ -965,7 +929,7 @@ describe('Dropin', () => {
       jest.spyOn(DropinModel.prototype, 'asyncDependencyStarting');
 
       Dropin.prototype._setUpThreeDSecure.call({
-        _client: testContext.client,
+        _authorization: 'fake-auth',
         _merchantConfiguration: {
           threeDSecure: {
             foo: 'bar'
@@ -1071,7 +1035,9 @@ describe('Dropin', () => {
         const instance = new Dropin(testContext.dropinOptions);
 
         instance._initialize(() => {
-          jest.spyOn(instance._mainView, 'requestPaymentMethod');
+          jest.spyOn(instance._mainView, 'requestPaymentMethod').mockResolvedValue({
+            nonce: 'fake-nonce'
+          });
           instance.requestPaymentMethod(() => {
             expect(instance._mainView.requestPaymentMethod).toBeCalledTimes(1);
             done();
@@ -1412,7 +1378,8 @@ describe('Dropin', () => {
         }
       };
       instance._model = {
-        getPaymentMethods: jest.fn().mockReturnValue([])
+        getPaymentMethods: jest.fn().mockReturnValue([]),
+        removeUnvaultedPaymentMethods: jest.fn()
       };
 
       instance.updateConfiguration('paypal', 'foo', 'bar');
@@ -1442,7 +1409,8 @@ describe('Dropin', () => {
         }
       };
       instance._model = {
-        getPaymentMethods: jest.fn().mockReturnValue([])
+        getPaymentMethods: jest.fn().mockReturnValue([]),
+        removeUnvaultedPaymentMethods: jest.fn()
       };
 
       instance.updateConfiguration('paypalCredit', 'foo', 'bar');
@@ -1472,7 +1440,8 @@ describe('Dropin', () => {
         }
       };
       instance._model = {
-        getPaymentMethods: jest.fn().mockReturnValue([])
+        getPaymentMethods: jest.fn().mockReturnValue([]),
+        removeUnvaultedPaymentMethods: jest.fn()
       };
 
       instance.updateConfiguration('applePay', 'foo', 'bar');
@@ -1502,7 +1471,8 @@ describe('Dropin', () => {
         }
       };
       instance._model = {
-        getPaymentMethods: jest.fn().mockReturnValue([])
+        getPaymentMethods: jest.fn().mockReturnValue([]),
+        removeUnvaultedPaymentMethods: jest.fn()
       };
 
       instance.updateConfiguration('googlePay', 'foo', 'bar');
@@ -1541,7 +1511,7 @@ describe('Dropin', () => {
     );
 
     test(
-      'removes saved paypal payment methods if they are not vaulted',
+      'removes only saved paypal payment methods if they are not vaulted',
       () => {
         const instance = new Dropin(testContext.dropinOptions);
         const getViewStub = jest.fn();
@@ -1568,7 +1538,8 @@ describe('Dropin', () => {
             { nonce: '4', type: 'PayPalAccount', vaulted: true },
             { nonce: '5', type: 'PayPalAccount' }
           ]),
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -1581,56 +1552,31 @@ describe('Dropin', () => {
 
         instance.updateConfiguration('paypal', 'foo', 'bar');
 
-        expect(instance._model.getPaymentMethods).toBeCalledTimes(1);
-        expect(instance._model.removePaymentMethod).toBeCalledTimes(2);
-        expect(instance._model.removePaymentMethod).toBeCalledWith({ nonce: '3', type: 'PayPalAccount' });
-        expect(instance._model.removePaymentMethod).toBeCalledWith({ nonce: '5', type: 'PayPalAccount' });
-      }
-    );
-
-    test(
-      'does not call removePaymentMethod if no non-vaulted paypal accounts are avaialble',
-      () => {
-        const instance = new Dropin(testContext.dropinOptions);
-        const getViewStub = jest.fn();
-        const fakePayPalView = {
-          updateConfiguration: jest.fn()
-        };
-        const fakeMethodsView = {
-          getPaymentMethod: jest.fn().mockReturnValue(null)
-        };
-
-        instance._mainView = {
-          getView: getViewStub,
-          primaryView: {
-            ID: 'view'
-          }
-        };
-        instance._model = {
-          getPaymentMethods: jest.fn().mockReturnValue([
-            { nonce: '1', type: 'PayPalAccount', vaulted: true },
-            { nonce: '2', type: 'CreditCard', vaulted: true },
-            { nonce: '3', type: 'PayPalAccount', vaulted: true }
-          ]),
-          removePaymentMethod: jest.fn()
-        };
+        expect(instance._model.removeUnvaultedPaymentMethods).toBeCalledTimes(1);
 
-        getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
-          if (arg === 'paypal') {
-            return fakePayPalView;
-          } else if (arg === 'methods') {
-            return fakeMethodsView;
-          }
-        });
+        const filter = instance._model.removeUnvaultedPaymentMethods.mock.calls[0][0];
 
-        instance.updateConfiguration('paypal', 'foo', 'bar');
+        expect(filter({
+          type: 'PayPalAccount'
+        })).toBe(true);
 
-        expect(instance._model.removePaymentMethod).not.toBeCalled();
+        expect(filter({
+          type: 'CreditCard'
+        })).toBe(false);
+        expect(filter({
+          type: 'AndroidPayCard'
+        })).toBe(false);
+        expect(filter({
+          type: 'ApplePayCard'
+        })).toBe(false);
+        expect(filter({
+          type: 'VenmoAccount'
+        })).toBe(false);
       }
     );
 
     test(
-      'removes saved applePay payment methods if they are not vaulted',
+      'removes only saved applePay payment methods if they are not vaulted',
       () => {
         const instance = new Dropin(testContext.dropinOptions);
         const getViewStub = jest.fn();
@@ -1657,7 +1603,8 @@ describe('Dropin', () => {
             { nonce: '4', type: 'ApplePayCard', vaulted: true },
             { nonce: '5', type: 'ApplePayCard' }
           ]),
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -1670,56 +1617,31 @@ describe('Dropin', () => {
 
         instance.updateConfiguration('applePay', 'foo', 'bar');
 
-        expect(instance._model.getPaymentMethods).toBeCalledTimes(1);
-        expect(instance._model.removePaymentMethod).toBeCalledTimes(2);
-        expect(instance._model.removePaymentMethod).toBeCalledWith({ nonce: '3', type: 'ApplePayCard' });
-        expect(instance._model.removePaymentMethod).toBeCalledWith({ nonce: '5', type: 'ApplePayCard' });
-      }
-    );
-
-    test(
-      'does not call removePaymentMethod if no non-vaulted applePay accounts are avaialble',
-      () => {
-        const instance = new Dropin(testContext.dropinOptions);
-        const getViewStub = jest.fn();
-        const fakeApplePayView = {
-          updateConfiguration: jest.fn()
-        };
-        const fakeMethodsView = {
-          getPaymentMethod: jest.fn().mockReturnValue(null)
-        };
-
-        instance._mainView = {
-          getView: getViewStub,
-          primaryView: {
-            ID: 'view'
-          }
-        };
-        instance._model = {
-          getPaymentMethods: jest.fn().mockReturnValue([
-            { nonce: '1', type: 'ApplePayCard', vaulted: true },
-            { nonce: '2', type: 'CreditCard', vaulted: true },
-            { nonce: '3', type: 'ApplePayCard', vaulted: true }
-          ]),
-          removePaymentMethod: jest.fn()
-        };
+        expect(instance._model.removeUnvaultedPaymentMethods).toBeCalledTimes(1);
 
-        getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
-          if (arg === 'applePay') {
-            return fakeApplePayView;
-          } else if (arg === 'methods') {
-            return fakeMethodsView;
-          }
-        });
+        const filter = instance._model.removeUnvaultedPaymentMethods.mock.calls[0][0];
 
-        instance.updateConfiguration('applePay', 'foo', 'bar');
+        expect(filter({
+          type: 'ApplePayCard'
+        })).toBe(true);
 
-        expect(instance._model.removePaymentMethod).not.toBeCalled();
+        expect(filter({
+          type: 'CreditCard'
+        })).toBe(false);
+        expect(filter({
+          type: 'PayPalAccount'
+        })).toBe(false);
+        expect(filter({
+          type: 'AndroidPayCard'
+        })).toBe(false);
+        expect(filter({
+          type: 'VenmoAccount'
+        })).toBe(false);
       }
     );
 
     test(
-      'removes saved googlePay payment methods if they are not vaulted',
+      'removes only saved googlePay payment methods if they are not vaulted',
       () => {
         const instance = new Dropin(testContext.dropinOptions);
         const getViewStub = jest.fn();
@@ -1746,7 +1668,8 @@ describe('Dropin', () => {
             { nonce: '4', type: 'AndroidPayCard', vaulted: true },
             { nonce: '5', type: 'AndroidPayCard' }
           ]),
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -1759,51 +1682,26 @@ describe('Dropin', () => {
 
         instance.updateConfiguration('googlePay', 'foo', 'bar');
 
-        expect(instance._model.getPaymentMethods).toBeCalledTimes(1);
-        expect(instance._model.removePaymentMethod).toBeCalledTimes(2);
-        expect(instance._model.removePaymentMethod).toBeCalledWith({ nonce: '3', type: 'AndroidPayCard' });
-        expect(instance._model.removePaymentMethod).toBeCalledWith({ nonce: '5', type: 'AndroidPayCard' });
-      }
-    );
-
-    test(
-      'does not call removePaymentMethod if no non-vaulted googlePay accounts are avaialble',
-      () => {
-        const instance = new Dropin(testContext.dropinOptions);
-        const getViewStub = jest.fn();
-        const fakeGooglePayView = {
-          updateConfiguration: jest.fn()
-        };
-        const fakeMethodsView = {
-          getPaymentMethod: jest.fn().mockReturnValue(null)
-        };
+        expect(instance._model.removeUnvaultedPaymentMethods).toBeCalledTimes(1);
 
-        instance._mainView = {
-          getView: getViewStub,
-          primaryView: {
-            ID: 'view'
-          }
-        };
-        instance._model = {
-          getPaymentMethods: jest.fn().mockReturnValue([
-            { nonce: '1', type: 'AndroidPayCard', vaulted: true },
-            { nonce: '2', type: 'CreditCard', vaulted: true },
-            { nonce: '3', type: 'AndroidPayCard', vaulted: true }
-          ]),
-          removePaymentMethod: jest.fn()
-        };
-
-        getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
-          if (arg === 'googlePay') {
-            return fakeGooglePayView;
-          } else if (arg === 'methods') {
-            return fakeMethodsView;
-          }
-        });
+        const filter = instance._model.removeUnvaultedPaymentMethods.mock.calls[0][0];
 
-        instance.updateConfiguration('googlePay', 'foo', 'bar');
+        expect(filter({
+          type: 'AndroidPayCard'
+        })).toBe(true);
 
-        expect(instance._model.removePaymentMethod).not.toBeCalled();
+        expect(filter({
+          type: 'CreditCard'
+        })).toBe(false);
+        expect(filter({
+          type: 'PayPalAccount'
+        })).toBe(false);
+        expect(filter({
+          type: 'ApplePayCard'
+        })).toBe(false);
+        expect(filter({
+          type: 'VenmoAccount'
+        })).toBe(false);
       }
     );
 
@@ -1831,7 +1729,8 @@ describe('Dropin', () => {
         instance._model = {
           getPaymentMethods: jest.fn().mockReturnValue([]),
           supportedPaymentOptions: ['paypal', 'card'],
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -1873,7 +1772,8 @@ describe('Dropin', () => {
         instance._model = {
           getPaymentMethods: jest.fn().mockReturnValue([]),
           supportedPaymentOptions: ['paypal'],
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -1914,7 +1814,8 @@ describe('Dropin', () => {
         };
         instance._model = {
           getPaymentMethods: jest.fn().mockReturnValue([]),
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -1956,7 +1857,8 @@ describe('Dropin', () => {
           getPaymentMethods: jest.fn().mockReturnValue([
             { nonce: '1', type: 'CreditCard' }
           ]),
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -2002,7 +1904,8 @@ describe('Dropin', () => {
         ]),
         refreshPaymentMethods: jest.fn().mockResolvedValue(),
         removeActivePaymentMethod: jest.fn(),
-        removePaymentMethod: jest.fn()
+        removePaymentMethod: jest.fn(),
+        removeUnvaultedPaymentMethods: jest.fn()
       };
 
       getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -2043,7 +1946,8 @@ describe('Dropin', () => {
             { nonce: '3', type: 'PayPalAccount', vaulted: true }
           ]),
           removeActivePaymentMethod: jest.fn(),
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -2083,7 +1987,8 @@ describe('Dropin', () => {
           getPaymentMethods: jest.fn().mockReturnValue([]),
           removeActivePaymentMethod: jest.fn(),
           supportedPaymentOptions: ['paypal', 'card'],
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -2124,7 +2029,8 @@ describe('Dropin', () => {
           getPaymentMethods: jest.fn().mockReturnValue([]),
           removeActivePaymentMethod: jest.fn(),
           supportedPaymentOptions: ['paypal'],
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -2164,7 +2070,8 @@ describe('Dropin', () => {
           refreshPaymentMethods: jest.fn().mockResolvedValue(),
           getPaymentMethods: jest.fn().mockReturnValue([]),
           removeActivePaymentMethod: jest.fn(),
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -2205,7 +2112,8 @@ describe('Dropin', () => {
             { nonce: '1', type: 'CreditCard' }
           ]),
           removeActivePaymentMethod: jest.fn(),
-          removePaymentMethod: jest.fn()
+          removePaymentMethod: jest.fn(),
+          removeUnvaultedPaymentMethods: jest.fn()
         };
 
         getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
@@ -2244,7 +2152,8 @@ describe('Dropin', () => {
           { nonce: '1', type: 'CreditCard' }
         ]),
         removeActivePaymentMethod: jest.fn(),
-        removePaymentMethod: jest.fn()
+        removePaymentMethod: jest.fn(),
+        removeUnvaultedPaymentMethods: jest.fn()
       };
 
       getViewStub.mockImplementation(arg => { // eslint-disable-line consistent-return
diff --git a/test/unit/index.js b/test/unit/index.js
index c6442a8..5b87730 100644
--- a/test/unit/index.js
+++ b/test/unit/index.js
@@ -1,27 +1,19 @@
+jest.mock('../../src/lib/analytics');
 
 const dropin = require('../../src/index');
 const Dropin = require('../../src/dropin');
 const DropinError = require('../../src/lib/dropin-error');
-const BraintreeError = require('braintree-web/lib/braintree-error');
-const client = require('braintree-web/client');
 const fake = require('../helpers/fake');
-const dropinConstants = require('../../src/constants');
 const analytics = require('../../src/lib/analytics');
 const {
   yields
 } = require('../helpers/yields');
-// TODO this gets transformed to the package.json version when built
-// should we figure out how to actually test this?
-const version = '__VERSION__';
 
 describe('dropin.create', () => {
   let testContext;
 
   beforeEach(() => {
     testContext = {};
-  });
-
-  beforeEach(() => {
     const container = document.createElement('div');
 
     testContext.form = document.createElement('form');
@@ -30,155 +22,51 @@ describe('dropin.create', () => {
 
     testContext.form.appendChild(container);
     document.body.appendChild(testContext.form);
-
-    jest.spyOn(client, 'create').mockResolvedValue();
   });
 
   afterEach(() => {
     document.body.removeChild(testContext.form);
   });
 
-  test('errors out if no authorization given', done => {
-    dropin.create({}, (err, instance) => {
-      expect(err).toBeInstanceOf(DropinError);
-      expect(err.message).toBe('options.authorization is required.');
-      expect(instance).not.toBeDefined();
-      done();
-    });
+  test('errors out if no authorization given', async () => {
+    await expect(dropin.create({})).rejects.toThrow('options.authorization is required.');
   });
 
-  test('returns an error if client.create errors', done => {
-    const originalErr = new BraintreeError({
-      type: 'MERCHANT',
-      code: 'CODE',
-      message: 'you goofed!!'
+  test('sets up analytics', async () => {
+    jest.spyOn(Dropin.prototype, '_initialize').mockImplementation(function (callback) {
+      callback(null, this); // eslint-disable-line no-invalid-this
     });
 
-    client.create.mockRejectedValue(originalErr);
-
-    dropin.create({
-      authorization: 'tokenization_key',
+    await dropin.create({
+      authorization: fake.tokenizationKey,
       selector: '#foo'
-    }, (err, instance) => {
-      expect(err).toBeInstanceOf(DropinError);
-      expect(err.message).toBe('There was an error creating Drop-in.');
-      expect(err._braintreeWebError).toBe(originalErr);
-      expect(instance).not.toBeDefined();
-      done();
     });
-  });
 
-  test('resolves a Dropin instance if client.create returns successfully', () => {
-    const fakeClient = fake.client();
+    expect(analytics.setupAnalytics).toBeCalledTimes(1);
+    expect(analytics.setupAnalytics).toBeCalledWith(fake.tokenizationKey);
+  });
 
+  test('resolves a Dropin instance', async () => {
     jest.spyOn(Dropin.prototype, '_initialize').mockImplementation(function (callback) {
       callback(null, this); // eslint-disable-line no-invalid-this
     });
 
-    client.create.mockResolvedValue(fakeClient);
-
-    return dropin.create({
-      authorization: 'tokenization_key',
+    const instance = await dropin.create({
+      authorization: fake.tokenizationKey,
       selector: '#foo'
-    }).then(instance => {
-      expect(instance).toBeInstanceOf(Dropin);
     });
-  }
-  );
-
-  test(
-    'returns an error to callback if Drop-in initialization fails',
-    done => {
-      const fakeClient = fake.client();
-      const dropinError = new DropinError('Dropin Error');
 
-      jest.spyOn(Dropin.prototype, '_initialize').mockImplementation(yields(dropinError));
-
-      client.create.mockResolvedValue(fakeClient);
-
-      dropin.create({
-        authorization: 'tokenization_key',
-        selector: '#foo'
-      }, (err, instance) => {
-        expect(err).toBe(dropinError);
-        expect(instance).not.toBeDefined();
-
-        done();
-      });
-    }
-  );
+    expect(instance).toBeInstanceOf(Dropin);
+  });
 
-  test('sets the correct analytics metadata', done => {
-    const fakeClient = fake.client();
+  test('returns an error to callback if Drop-in initialization fails', async () => {
+    const dropinError = new DropinError('Dropin Error');
 
-    jest.spyOn(Dropin.prototype, '_initialize').mockImplementation(function (callback) {
-      callback(null, this);
-    });
+    jest.spyOn(Dropin.prototype, '_initialize').mockImplementation(yields(dropinError));
 
-    client.create.mockResolvedValue(fakeClient);
-
-    dropin.create({
-      authorization: 'tokenization_key',
+    await expect(dropin.create({
+      authorization: fake.tokenizationKey,
       selector: '#foo'
-    }, () => {
-      const configuration = fakeClient.getConfiguration();
-
-      expect(configuration.analyticsMetadata.integration).toBe(dropinConstants.INTEGRATION);
-      expect(configuration.analyticsMetadata.integrationType).toBe(dropinConstants.INTEGRATION);
-      expect(configuration.analyticsMetadata.dropinVersion).toBe(version);
-
-      done();
-    });
+    })).rejects.toThrow(dropinError);
   });
-
-  test(
-    'sends web.dropin.started.tokenization-key event when using a tokenization key',
-    done => {
-      const fakeClient = fake.client();
-
-      jest.spyOn(Dropin.prototype, '_initialize').mockImplementation(function (callback) {
-        callback(null, this);
-      });
-      jest.spyOn(analytics, 'sendEvent');
-
-      client.create.mockResolvedValue(fakeClient);
-
-      dropin.create({
-        authorization: fake.tokenizationKey,
-        selector: '#foo'
-      }, () => {
-        expect(analytics.sendEvent).toBeCalledWith(fakeClient, 'started.tokenization-key');
-        done();
-      });
-    }
-  );
-
-  test(
-    'sends web.dropin.started.client-token event when using a client token',
-    done => {
-      let fakeConfiguration, fakeClient;
-
-      fakeConfiguration = fake.configuration();
-      fakeConfiguration.authorizationType = 'CLIENT_TOKEN';
-      fakeConfiguration.authorization = fake.clientToken;
-
-      fakeClient = fake.client();
-      fakeClient.getConfiguration.mockReturnValue(fakeConfiguration);
-
-      jest.spyOn(Dropin.prototype, '_initialize').mockImplementation(function (callback) {
-        callback(null, this);
-      });
-      jest.spyOn(analytics, 'sendEvent');
-
-      client.create.mockResolvedValue(fakeClient);
-
-      dropin.create({
-        authorization: fake.clientToken,
-        selector: '#foo'
-      }, () => {
-        expect(analytics.sendEvent).toBeCalledWith(fakeClient, 'started.client-token');
-        done();
-      });
-    }
-  );
 });
diff --git a/test/unit/lib/analytics.js b/test/unit/lib/analytics.js
index c351c51..c355d0f 100644
--- a/test/unit/lib/analytics.js
+++ b/test/unit/lib/analytics.js
@@ -1,50 +1,60 @@
-
+const client = require('braintree-web/client');
 const analytics = require('../../../src/lib/analytics');
-const atob = require('../../../src/lib/polyfill').atob;
 const braintreeClientVersion = require('braintree-web/client').VERSION;
 const constants = require('../../../src/constants');
 const fake = require('../../helpers/fake');
+const {
+  yieldsAsync
+} = require('../../helpers/yields');
 
-describe('analytics.sendEvent', () => {
+describe('analytics', () => {
   let testContext;
 
   beforeEach(() => {
     testContext = {};
-  });
-
-  beforeEach(() => {
     testContext.client = fake.client();
+    testContext.client._request.mockImplementation(yieldsAsync());
+    jest.spyOn(client, 'create').mockResolvedValue(testContext.client);
   });
 
-  test('correctly sends an analytics event with a callback', () => {
-    let postArgs, currentTimestamp;
-    const fakeConfiguration = fake.configuration();
+  describe('setupAnalytics', () => {
+    test('creates a client to use with sendEvent', async () => {
+      await analytics.setupAnalytics('fake-auth');
 
-    function callback() {}
+      expect(client.create).toBeCalledTimes(1);
+      expect(client.create).toBeCalledWith({
+        authorization: 'fake-auth'
+      });
+    });
 
-    analytics.sendEvent(testContext.client, 'test.event.kind', callback);
+    test('sets metadata on the client', async () => {
+      const clientInstance = await analytics.setupAnalytics('fake-auth');
 
-    expect(testContext.client._request).toBeCalled();
-    postArgs = testContext.client._request.mock.calls[0];
+      const config = clientInstance.getConfiguration();
 
-    expect(postArgs[0].url).toBe(fakeConfiguration.gatewayConfiguration.analytics.url);
-    expect(postArgs[0].method).toBe('post');
-    expect(postArgs[0].data.analytics[0].kind).toBe('web.dropin.test.event.kind');
-    expect(postArgs[0].data._meta.sessionId).toBe(fakeConfiguration.analyticsMetadata.sessionId);
-    currentTimestamp = Date.now() / 1000;
-    expect(currentTimestamp - postArgs[0].data.analytics[0].timestamp).toBeLessThan(2);
-    expect(currentTimestamp - postArgs[0].data.analytics[0].timestamp).toBeGreaterThan(0);
-    expect(postArgs[1]).toBe(callback);
-    expect(postArgs[0].timeout).toBe(constants.ANALYTICS_REQUEST_TIMEOUT_MS);
+      // this gets interpolated with the package version when built
+      expect(config.analyticsMetadata.dropinVersion).toBe('__VERSION__');
+      expect(config.analyticsMetadata.integration).toBe('dropin2');
+      expect(config.analyticsMetadata.integrationType).toBe('dropin2');
+    });
   });
 
-  test(
-    'correctly sends an analytics event with no callback (fire-and-forget)',
-    () => {
+  describe('sendEvent', () => {
+    beforeEach(async () => {
+      await analytics.setupAnalytics('fake-auth');
+    });
+
+    test('throws an error if client is not already setup', async () => {
+      analytics.resetClientPromise();
+
+      await expect(analytics.sendEvent('test.event.kind')).rejects.toThrow('Client not available.');
+    });
+
+    test('correctly sends an analytics event', async () => {
       let postArgs, currentTimestamp;
       const fakeConfiguration = fake.configuration();
 
-      analytics.sendEvent(testContext.client, 'test.event.kind');
+      await analytics.sendEvent('test.event.kind');
 
       expect(testContext.client._request).toBeCalled();
       postArgs = testContext.client._request.mock.calls[0];
@@ -57,63 +67,73 @@ describe('analytics.sendEvent', () => {
       expect(currentTimestamp - postArgs[0].data.analytics[0].timestamp).toBeLessThan(2);
       expect(currentTimestamp - postArgs[0].data.analytics[0].timestamp).toBeGreaterThan(0);
       expect(postArgs[0].timeout).toBe(constants.ANALYTICS_REQUEST_TIMEOUT_MS);
-    }
-  );
+    });
 
-  test('correctly formats _meta', () => {
-    let postArgs;
-    const fakeConfiguration = fake.configuration();
+    test('correctly formats _meta', async () => {
+      let postArgs;
+      const fakeConfiguration = fake.configuration();
 
-    analytics.sendEvent(testContext.client, 'test.event.kind');
+      fakeConfiguration.analyticsMetadata.dropinVersion = '__VERSION__';
+      fakeConfiguration.analyticsMetadata.integration = 'dropin2';
+      fakeConfiguration.analyticsMetadata.integrationType = 'dropin2';
 
-    expect(testContext.client._request).toBeCalled();
-    postArgs = testContext.client._request.mock.calls[0];
+      await analytics.sendEvent('test.event.kind');
 
-    expect(postArgs[0].data._meta).toEqual(fakeConfiguration.analyticsMetadata);
-  });
+      expect(testContext.client._request).toBeCalled();
+      postArgs = testContext.client._request.mock.calls[0];
+
+      expect(postArgs[0].data._meta).toEqual(fakeConfiguration.analyticsMetadata);
+    });
 
-  test('includes tokenizationKey', () => {
-    let client, postArgs;
-    const fakeConfiguration = fake.configuration();
+    test('includes tokenizationKey', async () => {
+      let postArgs;
+      const fakeConfiguration = fake.configuration();
 
-    fakeConfiguration.authorization = fake.tokenizationKey;
-    client = fake.client(fakeConfiguration);
+      fakeConfiguration.authorization = fake.tokenizationKey;
+      testContext.client.getConfiguration = jest.fn().mockReturnValue(fakeConfiguration);
 
-    analytics.sendEvent(client, 'test.event.kind');
+      // set up again with the new auth type
+      await analytics.setupAnalytics('fake-auth');
 
-    expect(client._request).toBeCalled();
-    postArgs = client._request.mock.calls[0];
+      await analytics.sendEvent('test.event.kind');
 
-    expect(postArgs[0].data.tokenizationKey).toBe(fakeConfiguration.authorization);
-    expect(postArgs[0].data.authorizationFingerprint).toBeFalsy();
-  });
+      expect(testContext.client._request).toBeCalled();
+      postArgs = testContext.client._request.mock.calls[0];
 
-  test('includes authorizationFingerprint', () => {
-    let client, fingerprint, postArgs;
-    const fakeConfiguration = fake.configuration();
+      expect(postArgs[0].data.tokenizationKey).toBe(fakeConfiguration.authorization);
+      expect(postArgs[0].data.authorizationFingerprint).toBeFalsy();
+    });
 
-    fakeConfiguration.authorization = fake.clientToken;
-    fakeConfiguration.authorizationType = 'CLIENT_TOKEN';
-    fingerprint = JSON.parse(atob(fakeConfiguration.authorization)).authorizationFingerprint;
-    client = fake.client(fakeConfiguration);
+    test('includes authorizationFingerprint', async () => {
+      let fingerprint, postArgs;
+      const fakeConfiguration = fake.configuration();
 
-    analytics.sendEvent(client, 'test.event.kind');
+      fakeConfiguration.authorization = fake.clientToken;
+      fakeConfiguration.authorizationType = 'CLIENT_TOKEN';
+      fingerprint = JSON.parse(window.atob(fakeConfiguration.authorization)).authorizationFingerprint;
+      testContext.client.getConfiguration = jest.fn().mockReturnValue(fakeConfiguration);
 
-    expect(client._request).toBeCalled();
-    postArgs = client._request.mock.calls[0];
+      // set up again with the new auth type
+      await analytics.setupAnalytics('fake-auth');
 
-    expect(postArgs[0].data.tokenizationKey).toBeFalsy();
-    expect(postArgs[0].data.authorizationFingerprint).toBe(fingerprint);
-  });
+      await analytics.sendEvent('test.event.kind');
 
-  test('includes braintreeLibraryVersion', () => {
-    let postArgs;
+      expect(testContext.client._request).toBeCalled();
+      postArgs = testContext.client._request.mock.calls[0];
+
+      expect(postArgs[0].data.tokenizationKey).toBeFalsy();
+      expect(postArgs[0].data.authorizationFingerprint).toBe(fingerprint);
+    });
 
-    analytics.sendEvent(testContext.client, 'test.event.kind');
+    test('includes braintreeLibraryVersion', async () => {
+      let postArgs;
 
-    expect(testContext.client._request).toBeCalled();
-    postArgs = testContext.client._request.mock.calls[0];
+      await analytics.sendEvent('test.event.kind');
+
+      expect(testContext.client._request).toBeCalled();
+      postArgs = testContext.client._request.mock.calls[0];
 
-    expect(postArgs[0].data.braintreeLibraryVersion).toBe(braintreeClientVersion);
+      expect(postArgs[0].data.braintreeLibraryVersion).toBe(braintreeClientVersion);
+    });
   });
 });
diff --git a/test/unit/lib/create-from-script-tag.js b/test/unit/lib/create-from-script-tag.js
index c556f37..41ed5aa 100644
--- a/test/unit/lib/create-from-script-tag.js
+++ b/test/unit/lib/create-from-script-tag.js
@@ -1,3 +1,4 @@
+jest.mock('../../../src/lib/analytics');
 
 const createFromScriptTag = require('../../../src/lib/create-from-script-tag');
 const findParentForm = require('../../../src/lib/find-parent-form');
@@ -17,9 +18,7 @@ describe('createFromScriptTag', () => {
     const container = document.createElement('div');
 
     container.id = 'script-container';
-    jest.spyOn(analytics, 'sendEvent').mockImplementation();
     testContext.instance = {
-      _client: 'fake-client',
       requestPaymentMethod: jest.fn().mockImplementation(yields(null, { nonce: 'a-nonce' }))
     };
     testContext.scriptTag = document.createElement('script');
@@ -110,7 +109,7 @@ describe('createFromScriptTag', () => {
 
     setTimeout(() => {
       expect(analytics.sendEvent).toBeCalledTimes(1);
-      expect(analytics.sendEvent).toBeCalledWith(testContext.instance._client, 'integration-type.script-tag');
+      expect(analytics.sendEvent).toBeCalledWith('integration-type.script-tag');
       done();
     });
   });
diff --git a/test/unit/lib/data-collector.js b/test/unit/lib/data-collector.js
index 81448e5..58313ed 100644
--- a/test/unit/lib/data-collector.js
+++ b/test/unit/lib/data-collector.js
@@ -1,4 +1,6 @@
+jest.mock('../../../src/lib/analytics');
 
+const btWebVersion = require('braintree-web/client').VERSION;
 const fake = require('../../helpers/fake');
 const assets = require('@braintree/asset-loader');
 const Promise = require('../../../src/lib/promise');
@@ -13,7 +15,6 @@ describe('DataCollector', () => {
   });
 
   beforeEach(() => {
-    jest.spyOn(analytics, 'sendEvent').mockImplementation();
     testContext.dataCollectorInstance = fake.dataCollectorInstance;
     jest.spyOn(testContext.dataCollectorInstance, 'teardown').mockResolvedValue();
   });
@@ -31,9 +32,7 @@ describe('DataCollector', () => {
       }
 
       testContext.config = {
-        client: {
-          getVersion: jest.fn().mockReturnValue('1.2.3')
-        },
+        authorization: 'fake-auth',
         kount: true
       };
 
@@ -60,7 +59,7 @@ describe('DataCollector', () => {
         return dc.initialize().then(() => {
           expect(assets.loadScript).toBeCalledTimes(1);
           expect(assets.loadScript).toBeCalledWith({
-            src: 'https://js.braintreegateway.com/web/1.2.3/js/data-collector.min.js',
+            src: `https://js.braintreegateway.com/web/${btWebVersion}/js/data-collector.min.js`,
             id: 'braintree-dropin-data-collector-script'
           });
         });
@@ -77,7 +76,7 @@ describe('DataCollector', () => {
         return dc.initialize().then(() => {
           expect(assets.loadScript).toBeCalledTimes(1);
           expect(assets.loadScript).toBeCalledWith({
-            src: 'https://js.braintreegateway.com/web/1.2.3/js/data-collector.min.js',
+            src: `https://js.braintreegateway.com/web/${btWebVersion}/js/data-collector.min.js`,
             id: 'braintree-dropin-data-collector-script'
           });
         });
@@ -92,13 +91,19 @@ describe('DataCollector', () => {
       });
     });
 
-    test('creates a data collector instance', () => {
+    test('creates a data collector instance using deferred method', () => {
       const dc = new DataCollector(testContext.config);
 
       expect(dc._instance).toBeFalsy();
 
       return dc.initialize().then(() => {
         expect(dc._instance).toBe(testContext.dataCollectorInstance);
+        expect(global.braintree.dataCollector.create).toBeCalledTimes(1);
+        expect(global.braintree.dataCollector.create).toBeCalledWith({
+          authorization: 'fake-auth',
+          kount: true,
+          useDeferredClient: true
+        });
       });
     });
 
@@ -112,18 +117,28 @@ describe('DataCollector', () => {
       return dc.initialize().then(() => {
         expect(dc._instance).toBeFalsy();
         expect(dc.log).toBeCalledWith(err);
-        expect(analytics.sendEvent).toBeCalledWith(testContext.config.client, 'data-collector.setup-failed');
+        expect(analytics.sendEvent).toBeCalledWith('data-collector.setup-failed');
       });
     });
   });
 
   describe('getDeviceData', () => {
-    test('returns device data', () => {
+    test('resolves with empty string when data collector instance is not avaialble', async () => {
+      const dc = new DataCollector({});
+
+      const data = await dc.getDeviceData();
+
+      expect(data).toBe('');
+    });
+
+    test('resolves device data', async () => {
       const dc = new DataCollector({});
 
       dc._instance = testContext.dataCollectorInstance;
 
-      expect(dc.getDeviceData()).toBe('device-data');
+      const data = await dc.getDeviceData();
+
+      expect(data).toBe('device-data');
     });
   });
 
diff --git a/test/unit/lib/is-guest-checkout.js b/test/unit/lib/is-guest-checkout.js
deleted file mode 100644
index 1c9dab4..0000000
--- a/test/unit/lib/is-guest-checkout.js
+++ /dev/null
@@ -1,38 +0,0 @@
-
-const fake = require('../../helpers/fake');
-const isGuestCheckout = require('../../../src/lib/is-guest-checkout');
-
-describe('isGuestCheckout', () => {
-  test('returns true when given a client with a tokenization key', () => {
-    const fakeClient = fake.client({
-      authorizationType: 'TOKENIZATION_KEY',
-      authorization: fake.tokenizationKey
-    });
-
-    expect(isGuestCheckout(fakeClient)).toBe(true);
-  });
-
-  test(
-    'returns true when given a client with a client token without a customer ID',
-    () => {
-      const fakeClient = fake.client({
-        authorizationType: 'CLIENT_TOKEN',
-        authorization: fake.clientToken
-      });
-
-      expect(isGuestCheckout(fakeClient)).toBe(true);
-    }
-  );
-
-  test(
-    'returns false when given a client with a client token with a customer ID',
-    () => {
-      const fakeClient = fake.client({
-        authorizationType: 'CLIENT_TOKEN',
-        authorization: fake.clientTokenWithCustomerID
-      });
-
-      expect(isGuestCheckout(fakeClient)).toBe(false);
-    }
-  );
-});
diff --git a/test/unit/lib/parse-authorization.js b/test/unit/lib/parse-authorization.js
new file mode 100644
index 0000000..daf60f3
--- /dev/null
+++ b/test/unit/lib/parse-authorization.js
@@ -0,0 +1,32 @@
+const parseAuthorization = require('../../../src/lib/parse-authorization');
+const fake = require('../../helpers/fake');
+
+describe('parseAuthorization', () => {
+  test('returns environment from tokenization key', () => {
+    expect(parseAuthorization(fake.tokenizationKey).environment).toBe('development');
+  });
+
+  test('returns authType from tokenization key', () => {
+    expect(parseAuthorization(fake.tokenizationKey).authType).toBe('TOKENIZATION_KEY');
+  });
+
+  test('returns hasCustomer field as false from tokenization key', () => {
+    expect(parseAuthorization(fake.tokenizationKey).hasCustomer).toBe(false);
+  });
+
+  test('returns environment from client token', () => {
+    expect(parseAuthorization(fake.clientToken).environment).toBe('development');
+  });
+
+  test('returns authType from client token', () => {
+    expect(parseAuthorization(fake.clientToken).authType).toBe('CLIENT_TOKEN');
+  });
+
+  test('returns hasCustomer field as false from client token without a customer', () => {
+    expect(parseAuthorization(fake.clientToken).hasCustomer).toBe(false);
+  });
+
+  test('returns hasCustomer field as true from client token with a customer', () => {
+    expect(parseAuthorization(fake.clientTokenWithCustomerID).hasCustomer).toBe(true);
+  });
+});
diff --git a/test/unit/lib/polyfill.js b/test/unit/lib/polyfill.js
deleted file mode 100644
index 1ca89b0..0000000
--- a/test/unit/lib/polyfill.js
+++ /dev/null
@@ -1,21 +0,0 @@
-
-const atob = require('../../../src/lib/polyfill')._atob;
-
-describe('Polyfill', () => {
-  describe('atob', () => {
-    test('decodes a base64 encoded string', () => {
-      const base64Encoded = btoa('hello world');
-      const decoded = atob(base64Encoded);
-
-      expect(decoded).toBe('hello world');
-    });
-
-    test('raises an exception if the string is not base64 encoded', () => {
-      const error = /Non base64 encoded input passed to window.atob polyfill/;
-
-      expect(() => {
-        atob('not-base64-encoded');
-      }).toThrowError(error);
-    });
-  });
-});
diff --git a/test/unit/lib/three-d-secure.js b/test/unit/lib/three-d-secure.js
index a0ce10e..58b9d96 100644
--- a/test/unit/lib/three-d-secure.js
+++ b/test/unit/lib/three-d-secure.js
@@ -28,13 +28,13 @@ describe('ThreeDSecure', () => {
 
     test('sets up three d secure', () => {
       const config = {};
-      const client = {};
-      const tds = new ThreeDSecure(client, config, 'Card Verification');
+      const auth = 'fake-auth';
+      const tds = new ThreeDSecure(auth, config, 'Card Verification');
 
       return tds.initialize().then(() => {
         expect(threeDSecure.create).toBeCalledTimes(1);
         expect(threeDSecure.create).toBeCalledWith({
-          client: client,
+          authorization: 'fake-auth',
           version: 2
         });
         expect(tds._instance).toBe(testContext.threeDSecureInstance);
@@ -45,7 +45,7 @@ describe('ThreeDSecure', () => {
   describe('verify', () => {
     beforeEach(() => {
       testContext.config = {
-        client: {},
+        authorization: 'fake-auth',
         amount: '10.00'
       };
 
diff --git a/test/unit/views/base-view.js b/test/unit/views/base-view.js
index 81641e8..2191e8a 100644
--- a/test/unit/views/base-view.js
+++ b/test/unit/views/base-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../src/lib/analytics');
 
 const BaseView = require('../../../src/views/base-view');
 const constants = require('../../../src/constants');
diff --git a/test/unit/views/delete-confirmation-view.js b/test/unit/views/delete-confirmation-view.js
index 86453b2..c4ba7a0 100644
--- a/test/unit/views/delete-confirmation-view.js
+++ b/test/unit/views/delete-confirmation-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../src/lib/analytics');
 
 const BaseView = require('../../../src/views/base-view');
 const DeleteConfirmationView = require('../../../src/views/delete-confirmation-view');
diff --git a/test/unit/views/main-view.js b/test/unit/views/main-view.js
index aa8e9d4..16b9d01 100644
--- a/test/unit/views/main-view.js
+++ b/test/unit/views/main-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../src/lib/analytics');
 
 const MainView = require('../../../src/views/main-view');
 const ApplePayView = require('../../../src/views/payment-sheet-views/apple-pay-view');
@@ -30,10 +31,8 @@ describe('MainView', () => {
 
   beforeEach(() => {
     testContext = {};
-    testContext.client = fake.client();
     jest.spyOn(CardView.prototype, 'getPaymentMethod').mockImplementation();
     jest.spyOn(BasePayPalView.prototype, 'initialize').mockImplementation();
-    jest.spyOn(analytics, 'sendEvent').mockImplementation();
   });
 
   describe('Constructor', () => {
@@ -59,7 +58,6 @@ describe('MainView', () => {
       element.innerHTML = templateHTML;
 
       testContext.mainViewOptions = {
-        client: testContext.client,
         element: element,
         merchantConfiguration: {
           authorization: fake.tokenizationKey
@@ -162,10 +160,13 @@ describe('MainView', () => {
     describe('with vaulted payment methods', () => {
       beforeEach(() => {
         const element = document.createElement('div');
+        const fakeModelOptions = fake.modelOptions();
+
+        fakeModelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
 
         element.innerHTML = templateHTML;
 
-        testContext.model = fake.model();
+        testContext.model = fake.model(fakeModelOptions);
 
         testContext.model.getVaultedPaymentMethods.mockResolvedValue([
           { type: 'CreditCard', details: { lastTwo: '11' }},
@@ -173,7 +174,6 @@ describe('MainView', () => {
         ]);
 
         testContext.dropinOptions = {
-          client: testContext.client,
           merchantConfiguration: {
             container: '#foo',
             authorization: fake.tokenizationKey
@@ -184,7 +184,6 @@ describe('MainView', () => {
           testContext.model.supportedPaymentOptions = ['card', 'paypal'];
 
           testContext.mainViewOptions = {
-            client: testContext.client,
             element: element,
             merchantConfiguration: {
               authorization: fake.tokenizationKey
@@ -212,7 +211,9 @@ describe('MainView', () => {
         'does not set the first payment method to be the active payment method if configured not to',
         () => {
           jest.spyOn(testContext.model, 'changeActivePaymentMethod');
-          testContext.model.merchantConfiguration.preselectVaultedPaymentMethod = false;
+          testContext.model.vaultManagerConfig = {
+            preselectVaultedPaymentMethod: false
+          };
           jest.spyOn(MainView.prototype, 'setPrimaryView').mockImplementation();
 
           new MainView(testContext.mainViewOptions); // eslint-disable-line no-new
@@ -226,21 +227,25 @@ describe('MainView', () => {
       test(
         'sends preselect analytic event when a vaulted card is preselected',
         () => {
-          testContext.model.merchantConfiguration.preselectVaultedPaymentMethod = true;
+          testContext.model.vaultManagerConfig = {
+            preselectVaultedPaymentMethod: true
+          };
           new MainView(testContext.mainViewOptions); // eslint-disable-line no-new
 
-          expect(analytics.sendEvent).toBeCalledWith(testContext.client, 'vaulted-card.preselect');
+          expect(analytics.sendEvent).toBeCalledWith('vaulted-card.preselect');
         }
       );
 
       test(
         'does not send preselect analytic event when a vaulted card is not preselected',
         () => {
-          testContext.model.merchantConfiguration.preselectVaultedPaymentMethod = false;
+          testContext.model.vaultManagerConfig = {
+            preselectVaultedPaymentMethod: false
+          };
 
           new MainView(testContext.mainViewOptions); // eslint-disable-line no-new
 
-          expect(analytics.sendEvent).not.toBeCalledWith(testContext.client, 'vaulted-card.preselect');
+          expect(analytics.sendEvent).not.toBeCalledWith('vaulted-card.preselect');
         }
       );
 
@@ -264,7 +269,6 @@ describe('MainView', () => {
 
         return testContext.model.initialize().then(() => {
           testContext.mainViewOptions = {
-            client: testContext.client,
             element: element,
             merchantConfiguration: {
               authorization: fake.tokenizationKey
@@ -338,7 +342,6 @@ describe('MainView', () => {
         testContext.mainViewOptions = {
           element: wrapper,
           model: model,
-          client: testContext.client,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
           },
@@ -684,7 +687,6 @@ describe('MainView', () => {
           _sendToDefaultView: jest.fn(),
           _onChangeActivePaymentMethodView: jest.fn(),
           model: model,
-          client: fake.client(),
           setPrimaryView: jest.fn(),
           showSheetError: jest.fn(),
           allowUserAction: jest.fn(),
@@ -766,7 +768,6 @@ describe('MainView', () => {
         testContext.mainViewOptions = {
           element: testContext.element,
           model: testContext.model,
-          client: testContext.client,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
           },
@@ -920,7 +921,6 @@ describe('MainView', () => {
       return model.initialize().then(() => {
         testContext.mainViewOptions = {
           element: testContext.wrapper,
-          client: testContext.client,
           model: model,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
@@ -1038,7 +1038,6 @@ describe('MainView', () => {
         testContext.mainView = new MainView({
           element: testContext.wrapper,
           model: model,
-          client: testContext.client,
           merchantConfiguration: {
             authorization: 'fake_tokenization_key'
           },
@@ -1064,7 +1063,7 @@ describe('MainView', () => {
         throw new Error('should not resolve');
       }).catch(err => {
         expect(err).toBe(fakeError);
-        expect(analytics.sendEvent).toBeCalledWith(testContext.client, 'request-payment-method.error');
+        expect(analytics.sendEvent).toBeCalledWith('request-payment-method.error');
       });
     });
 
@@ -1084,7 +1083,7 @@ describe('MainView', () => {
       jest.spyOn(CardView.prototype, 'requestPaymentMethod').mockResolvedValue(stubPaymentMethod);
 
       return testContext.mainView.requestPaymentMethod().then(() => {
-        expect(analytics.sendEvent).toBeCalledWith(testContext.client, 'request-payment-method.card');
+        expect(analytics.sendEvent).toBeCalledWith('request-payment-method.card');
       });
     });
 
@@ -1094,7 +1093,7 @@ describe('MainView', () => {
       jest.spyOn(CardView.prototype, 'requestPaymentMethod').mockResolvedValue(stubPaymentMethod);
 
       return testContext.mainView.requestPaymentMethod().then(() => {
-        expect(analytics.sendEvent).toBeCalledWith(testContext.client, 'request-payment-method.paypal');
+        expect(analytics.sendEvent).toBeCalledWith('request-payment-method.paypal');
       });
     });
 
@@ -1111,7 +1110,6 @@ describe('MainView', () => {
 
           testContext.mainView = new MainView({
             element: testContext.wrapper,
-            client: testContext.client,
             model: model,
             merchantConfiguration: {
               authorization: fake.clientTokenWithCustomerID
@@ -1253,7 +1251,6 @@ describe('MainView', () => {
       return model.initialize().then(() => {
         model.supportedPaymentOptions = ['card'];
         testContext.mainViewOptions = {
-          client: testContext.client,
           element: element,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
@@ -1302,7 +1299,6 @@ describe('MainView', () => {
       return model.initialize().then(() => {
         model.supportedPaymentOptions = ['card'];
         testContext.mainViewOptions = {
-          client: testContext.client,
           element: element,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
@@ -1376,7 +1372,6 @@ describe('MainView', () => {
       return model.initialize().then(() => {
         model.supportedPaymentOptions = ['card'];
         testContext.mainViewOptions = {
-          client: testContext.client,
           element: element,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
@@ -1420,7 +1415,6 @@ describe('MainView', () => {
       return model.initialize().then(() => {
         model.supportedPaymentOptions = ['card'];
         testContext.mainViewOptions = {
-          client: testContext.client,
           element: element,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
@@ -1451,7 +1445,6 @@ describe('MainView', () => {
       return model.initialize().then(() => {
         model.supportedPaymentOptions = ['card'];
         testContext.mainViewOptions = {
-          client: testContext.client,
           element: element,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
@@ -1488,7 +1481,6 @@ describe('MainView', () => {
       return model.initialize().then(() => {
         model.supportedPaymentOptions = ['card'];
         testContext.mainViewOptions = {
-          client: testContext.client,
           element: element,
           merchantConfiguration: {
             authorization: fake.tokenizationKey
diff --git a/test/unit/views/payment-method-view.js b/test/unit/views/payment-method-view.js
index 94ac245..51f862e 100644
--- a/test/unit/views/payment-method-view.js
+++ b/test/unit/views/payment-method-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../src/lib/analytics');
 
 const analytics = require('../../../src/lib/analytics');
 const BaseView = require('../../../src/views/base-view');
@@ -13,9 +14,6 @@ describe('PaymentMethodView', () => {
 
   beforeEach(() => {
     testContext = {};
-  });
-
-  beforeEach(() => {
     testContext.div = document.createElement('div');
     testContext.div.innerHTML = paymentMethodHTML;
     document.body.appendChild(testContext.div);
@@ -236,14 +234,11 @@ describe('PaymentMethodView', () => {
 
   describe('selecting payment methods', () => {
     beforeEach(() => {
-      testContext.client = fake.client();
       testContext.model = fake.model();
-      jest.spyOn(analytics, 'sendEvent').mockImplementation();
     });
 
     test('sends an analytic event when a vaulted card is selected', () => {
       const view = new PaymentMethodView({
-        client: testContext.client,
         model: testContext.model,
         strings: strings,
         paymentMethod: {
@@ -258,14 +253,13 @@ describe('PaymentMethodView', () => {
 
       view._choosePaymentMethod();
 
-      expect(analytics.sendEvent).toBeCalledWith(testContext.client, 'vaulted-card.select');
+      expect(analytics.sendEvent).toBeCalledWith('vaulted-card.select');
     });
 
     test(
       'sends an analytic event when a vaulted paypal payment method is selected',
       () => {
         const view = new PaymentMethodView({
-          client: testContext.client,
           model: testContext.model,
           strings: strings,
           paymentMethod: {
@@ -279,7 +273,7 @@ describe('PaymentMethodView', () => {
 
         view._choosePaymentMethod();
 
-        expect(analytics.sendEvent).toBeCalledWith(testContext.client, 'vaulted-paypal.select');
+        expect(analytics.sendEvent).toBeCalledWith('vaulted-paypal.select');
       }
     );
 
@@ -287,7 +281,6 @@ describe('PaymentMethodView', () => {
       'does not send an analytic event when no payment is selected',
       () => {
         const view = new PaymentMethodView({
-          client: testContext.client,
           model: testContext.model,
           strings: strings,
           paymentMethod: {}
@@ -295,7 +288,7 @@ describe('PaymentMethodView', () => {
 
         view._choosePaymentMethod();
 
-        expect(analytics.sendEvent).not.toBeCalledWith(testContext.client, 'vaulted-card.select');
+        expect(analytics.sendEvent).not.toBeCalled();
       }
     );
 
@@ -303,7 +296,6 @@ describe('PaymentMethodView', () => {
       'does not send an analytic event when a non-vaulted PayPal payment method is selected',
       () => {
         const view = new PaymentMethodView({
-          client: testContext.client,
           model: testContext.model,
           strings: strings,
           paymentMethod: {
@@ -317,7 +309,7 @@ describe('PaymentMethodView', () => {
 
         view._choosePaymentMethod();
 
-        expect(analytics.sendEvent).not.toBeCalledWith(testContext.client, 'vaulted-paypal.select');
+        expect(analytics.sendEvent).not.toBeCalled();
       }
     );
   });
diff --git a/test/unit/views/payment-methods-view.js b/test/unit/views/payment-methods-view.js
index a880b37..3b1a2d4 100644
--- a/test/unit/views/payment-methods-view.js
+++ b/test/unit/views/payment-methods-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../src/lib/analytics');
 
 const BaseView = require('../../../src/views/base-view');
 const CardView = require('../../../src/views/payment-sheet-views/card-view');
@@ -44,11 +45,6 @@ describe('PaymentMethodsView', () => {
       let model, paymentMethodsViews;
       const modelOptions = fake.modelOptions();
 
-      modelOptions.client.getConfiguration.mockReturnValue({
-        authorization: fake.clientTokenWithCustomerID,
-        authorizationType: 'CLIENT_TOKEN',
-        gatewayConfiguration: fake.configuration().gatewayConfiguration
-      });
       modelOptions.merchantConfiguration.paypal = { flow: 'vault' };
 
       model = fake.model(modelOptions);
@@ -85,11 +81,6 @@ describe('PaymentMethodsView', () => {
       };
       const modelOptions = fake.modelOptions();
 
-      modelOptions.client.getConfiguration.mockReturnValue({
-        authorization: fake.clientTokenWithCustomerID,
-        authorizationType: 'CLIENT_TOKEN',
-        gatewayConfiguration: fake.configuration().gatewayConfiguration
-      });
       modelOptions.merchantConfiguration.paypal = { flow: 'vault' };
 
       model = fake.model(modelOptions);
@@ -114,12 +105,6 @@ describe('PaymentMethodsView', () => {
       let model, methodsContainer, paymentMethodsViews;
       const modelOptions = fake.modelOptions();
 
-      modelOptions.client.getConfiguration.mockReturnValue({
-        authorization: fake.clientTokenWithCustomerID,
-        authorizationType: 'CLIENT_TOKEN',
-        gatewayConfiguration: fake.configuration().gatewayConfiguration
-      });
-
       model = fake.model(modelOptions);
 
       return model.initialize().then(() => {
@@ -190,8 +175,6 @@ describe('PaymentMethodsView', () => {
         model.getVaultedPaymentMethods.mockResolvedValue([fakePayPal, fakeCard]);
 
         return model.initialize().then(() => {
-          model.isGuestCheckout = false;
-
           paymentMethodsViews = new PaymentMethodsView({
             element: testContext.element,
             model: model,
@@ -233,12 +216,6 @@ describe('PaymentMethodsView', () => {
         const methodsContainer = testContext.element.querySelector('[data-braintree-id="methods-container"]');
         const modelOptions = fake.modelOptions();
 
-        modelOptions.client.getConfiguration.mockReturnValue({
-          authorization: fake.clientTokenWithCustomerID,
-          authorizationType: 'CLIENT_TOKEN',
-          gatewayConfiguration: fake.configuration().gatewayConfiguration
-        });
-
         modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
         model = fake.model(modelOptions);
 
@@ -262,18 +239,14 @@ describe('PaymentMethodsView', () => {
       }
     );
 
-    test('removes other payment methods in guest checkout', () => {
-      let model, paymentMethodsViews;
-      const methodsContainer = testContext.element.querySelector('[data-braintree-id="methods-container"]');
+    test('removes unvaulted payment methods when adding a new payment method', () => {
       const modelOptions = fake.modelOptions();
+      const model = fake.model(modelOptions);
 
-      modelOptions.merchantConfiguration.authorization = fake.clientToken;
-      model = fake.model(modelOptions);
-
-      model.getVaultedPaymentMethods.mockResolvedValue([testContext.fakePaymentMethod]);
+      jest.spyOn(model, 'removeUnvaultedPaymentMethods').mockImplementation();
 
       return model.initialize().then(() => {
-        paymentMethodsViews = new PaymentMethodsView({
+        new PaymentMethodsView({ // eslint-disable-line no-new
           element: testContext.element,
           model: model,
           merchantConfiguration: {
@@ -282,10 +255,12 @@ describe('PaymentMethodsView', () => {
           strings: strings
         });
 
-        model.addPaymentMethod({ foo: 'bar' });
+        model.addPaymentMethod({ nonce: 'fake-nonce' });
+        const removeUnvaultedPaymentMethodsFilter = model.removeUnvaultedPaymentMethods.mock.calls[0][0];
 
-        expect(paymentMethodsViews.views.length).toBe(1);
-        expect(methodsContainer.childElementCount).toBe(1);
+        expect(model.removeUnvaultedPaymentMethods).toBeCalledTimes(1);
+        expect(removeUnvaultedPaymentMethodsFilter({ nonce: 'other-nonce' })).toBe(true);
+        expect(removeUnvaultedPaymentMethodsFilter({ nonce: 'fake-nonce' })).toBe(false);
       });
     });
 
@@ -521,11 +496,7 @@ describe('PaymentMethodsView', () => {
 
       element.innerHTML = mainHTML;
 
-      modelOptions.client.getConfiguration.mockReturnValue({
-        authorization: fake.clientTokenWithCustomerID,
-        authorizationType: 'CLIENT_TOKEN',
-        gatewayConfiguration: fake.configuration().gatewayConfiguration
-      });
+      modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
       modelOptions.merchantConfiguration.paypal = { flow: 'vault' };
 
       model = fake.model(modelOptions);
@@ -565,11 +536,7 @@ describe('PaymentMethodsView', () => {
 
       element.innerHTML = mainHTML;
 
-      modelOptions.client.getConfiguration.mockReturnValue({
-        authorization: fake.clientTokenWithCustomerID,
-        authorizationType: 'CLIENT_TOKEN',
-        gatewayConfiguration: fake.configuration().gatewayConfiguration
-      });
+      modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
       modelOptions.merchantConfiguration.paypal = { flow: 'vault' };
 
       model = fake.model(modelOptions);
@@ -608,11 +575,7 @@ describe('PaymentMethodsView', () => {
 
       element.innerHTML = mainHTML;
 
-      modelOptions.client.getConfiguration.mockReturnValue({
-        authorization: fake.clientTokenWithCustomerID,
-        authorizationType: 'CLIENT_TOKEN',
-        gatewayConfiguration: fake.configuration().gatewayConfiguration
-      });
+      modelOptions.merchantConfiguration.authorization = fake.clientTokenWithCustomerID;
       modelOptions.merchantConfiguration.paypal = { flow: 'vault' };
 
       testContext.model = fake.model(modelOptions);
diff --git a/test/unit/views/payment-options-view.js b/test/unit/views/payment-options-view.js
index c257927..c786fa1 100644
--- a/test/unit/views/payment-options-view.js
+++ b/test/unit/views/payment-options-view.js
@@ -1,11 +1,12 @@
+jest.mock('../../../src/lib/analytics');
 
+const analytics = require('../../../src/lib/analytics');
 const BaseView = require('../../../src/views/base-view');
 const CardView = require('../../../src/views/payment-sheet-views/card-view');
 const PaymentOptionsView = require('../../../src/views/payment-options-view');
 const strings = require('../../../src/translations/en_US');
 const fake = require('../../helpers/fake');
 const fs = require('fs');
-const analytics = require('../../../src/lib/analytics');
 
 const mainHTML = fs.readFileSync(__dirname + '/../../../src/html/main.html', 'utf8');
 
@@ -56,10 +57,6 @@ describe('PaymentOptionsView', () => {
     testContext = {};
   });
 
-  beforeEach(() => {
-    testContext.client = fake.client();
-  });
-
   describe('Constructor', () => {
     beforeEach(() => {
       jest.spyOn(PaymentOptionsView.prototype, '_initialize').mockImplementation();
@@ -93,7 +90,6 @@ describe('PaymentOptionsView', () => {
 
       test('adds a ' + option.paymentOptionID + ' option', () => {
         const paymentOptionsView = new PaymentOptionsView({
-          client: testContext.client,
           element: testContext.element,
           mainView: {},
           model: modelThatSupports([option.paymentOptionID]),
@@ -121,7 +117,6 @@ describe('PaymentOptionsView', () => {
     test('sets the primary view to the payment option when clicked', () => {
       const mainViewStub = { setPrimaryView: jest.fn() };
       const paymentOptionsView = new PaymentOptionsView({
-        client: testContext.client,
         element: testContext.element,
         mainView: mainViewStub,
         model: modelThatSupports(['card']),
@@ -139,7 +134,6 @@ describe('PaymentOptionsView', () => {
       () => {
         const mainViewStub = { setPrimaryView: jest.fn() };
         const paymentOptionsView = new PaymentOptionsView({
-          client: testContext.client,
           element: testContext.element,
           mainView: mainViewStub,
           model: modelThatSupports(['card']),
@@ -165,7 +159,6 @@ describe('PaymentOptionsView', () => {
       testContext.element = wrapper.querySelector('[data-braintree-id="' + PaymentOptionsView.ID + '"]');
 
       testContext.viewConfiguration = {
-        client: testContext.client,
         element: testContext.element,
         mainView: { setPrimaryView: function () {} },
         strings: strings
@@ -183,15 +176,13 @@ describe('PaymentOptionsView', () => {
           const viewConfiguration = testContext.viewConfiguration;
           const eventName = 'selected.' + option.paymentOptionID;
 
-          jest.spyOn(analytics, 'sendEvent').mockImplementation();
-
           viewConfiguration.model = model;
           paymentOptionsView = new PaymentOptionsView(viewConfiguration);
           optionElement = paymentOptionsView.container.querySelector('.braintree-option');
 
           optionElement.click();
 
-          expect(analytics.sendEvent).toBeCalledWith(paymentOptionsView.client, eventName);
+          expect(analytics.sendEvent).toBeCalledWith(eventName);
         }
       );
     });
diff --git a/test/unit/views/payment-sheet-views/apple-pay-view.js b/test/unit/views/payment-sheet-views/apple-pay-view.js
index 630e4a6..b9c0800 100644
--- a/test/unit/views/payment-sheet-views/apple-pay-view.js
+++ b/test/unit/views/payment-sheet-views/apple-pay-view.js
@@ -1,6 +1,7 @@
-
 /* eslint-disable no-new */
 
+jest.mock('../../../../src/lib/analytics');
+
 const BaseView = require('../../../../src/views/base-view');
 const ApplePayView = require('../../../../src/views/payment-sheet-views/apple-pay-view');
 const btApplePay = require('braintree-web/apple-pay');
@@ -21,8 +22,6 @@ describe('ApplePayView', () => {
     testContext.model = fake.model();
 
     return testContext.model.initialize().then(() => {
-      testContext.fakeClient = fake.client();
-
       testContext.div = document.createElement('div');
 
       testContext.fakeApplePaySession = {
@@ -51,7 +50,6 @@ describe('ApplePayView', () => {
         displayName: 'Unit Test Display Name'
       };
       testContext.applePayViewOptions = {
-        client: testContext.fakeClient,
         element: document.body.querySelector('.braintree-sheet.braintree-applePay'),
         model: testContext.model,
         strings: {}
@@ -100,7 +98,8 @@ describe('ApplePayView', () => {
     test('creates an ApplePay component', () => {
       return testContext.view.initialize().then(() => {
         expect(btApplePay.create).toBeCalledWith(expect.objectContaining({
-          client: testContext.view.client
+          authorization: testContext.view.model.authorization,
+          useDeferredClient: true
         }));
         expect(testContext.view.applePayInstance).toBe(testContext.fakeApplePayInstance);
       });
@@ -156,10 +155,7 @@ describe('ApplePayView', () => {
     test(
       'reports error when canMakePaymentsWithActiveCard returns false in production mode',
       done => {
-        const configuration = fake.configuration();
-
-        configuration.gatewayConfiguration.environment = 'production';
-        testContext.fakeClient.getConfiguration.mockReturnValue(configuration);
+        testContext.model.environment = 'production';
 
         global.ApplePaySession.canMakePaymentsWithActiveCard = jest.fn().mockResolvedValue(false);
 
@@ -176,10 +172,7 @@ describe('ApplePayView', () => {
     test(
       'reports developer error when canMakePaymentsWithActiveCard returns false in sandbox mode',
       done => {
-        const configuration = fake.configuration();
-
-        configuration.gatewayConfiguration.environment = 'sandbox';
-        testContext.fakeClient.getConfiguration.mockReturnValue(configuration);
+        testContext.model.environment = 'sandbox';
 
         jest.spyOn(console, 'error').mockImplementation();
         global.ApplePaySession.canMakePaymentsWithActiveCard = jest.fn().mockResolvedValue(false);
@@ -414,27 +407,11 @@ describe('ApplePayView', () => {
   describe('isEnabled', () => {
     beforeEach(() => {
       testContext.options = {
-        client: testContext.fakeClient,
         merchantConfiguration: testContext.model.merchantConfiguration
       };
       jest.spyOn(isHTTPS, 'isHTTPS').mockReturnValue(true);
     });
 
-    test(
-      'resolves with false when Apple Pay is not enabled on the gateway',
-      () => {
-        const configuration = fake.configuration();
-
-        delete configuration.gatewayConfiguration.applePayWeb;
-
-        testContext.fakeClient.getConfiguration.mockReturnValue(configuration);
-
-        return ApplePayView.isEnabled(testContext.options).then(result => {
-          expect(result).toBe(false);
-        });
-      }
-    );
-
     test(
       'resolves with false when Apple Pay is not enabled by merchant',
       () => {
diff --git a/test/unit/views/payment-sheet-views/base-paypal-view.js b/test/unit/views/payment-sheet-views/base-paypal-view.js
index 477a628..de691c3 100644
--- a/test/unit/views/payment-sheet-views/base-paypal-view.js
+++ b/test/unit/views/payment-sheet-views/base-paypal-view.js
@@ -1,7 +1,9 @@
+jest.mock('../../../../src/lib/analytics');
 
 /* eslint-disable no-new */
 
-const analytics = require('../../../../src/lib/analytics');
+// TODO should we assert on some analytics?
+// const analytics = require('../../../../src/lib/analytics');
 const browserDetection = require('../../../../src/lib/browser-detection');
 const BaseView = require('../../../../src/views/base-view');
 const DropinModel = require('../../../../src/dropin-model');
@@ -35,7 +37,6 @@ describe('BasePayPalView', () => {
         CREDIT: 'credit'
       }
     };
-    jest.spyOn(analytics, 'sendEvent').mockImplementation();
 
     global.paypal = testContext.paypal;
 
@@ -51,18 +52,18 @@ describe('BasePayPalView', () => {
     jest.spyOn(testContext.model, 'reportError').mockImplementation();
 
     testContext.configuration = fake.configuration();
-    testContext.fakeClient = fake.client(testContext.configuration);
     testContext.paypalViewOptions = {
       strings: {},
       element: testContext.element,
-      model: testContext.model,
-      client: testContext.fakeClient
+      model: testContext.model
     };
     testContext.paypalInstance = {
       createPayment: jest.fn().mockResolvedValue(),
       tokenizePayment: jest.fn().mockResolvedValue()
     };
     jest.spyOn(PayPalCheckout, 'create').mockResolvedValue(testContext.paypalInstance);
+
+    return testContext.model.initialize();
   });
 
   afterEach(() => {
@@ -106,7 +107,7 @@ describe('BasePayPalView', () => {
     test('creates a PayPal Checkout component', () => {
       return testContext.view.initialize().then(() => {
         expect(PayPalCheckout.create).toBeCalledWith(expect.objectContaining({
-          client: testContext.paypalViewOptions.client
+          authorization: testContext.view.model.authorization
         }));
         expect(testContext.view.paypalInstance).toBe(testContext.paypalInstance);
       });
@@ -265,7 +266,7 @@ describe('BasePayPalView', () => {
     test(
       'sets paypal-checkout.js environment to production when gatewayConfiguration is production',
       () => {
-        testContext.configuration.gatewayConfiguration.environment = 'production';
+        testContext.view.model.environment = 'production';
 
         return testContext.view.initialize().then(() => {
           expect(testContext.paypal.Button.render).toBeCalledWith(expect.objectContaining({
@@ -278,7 +279,7 @@ describe('BasePayPalView', () => {
     test(
       'sets paypal-checkout.js environment to sandbox when gatewayConfiguration is not production',
       () => {
-        testContext.configuration.gatewayConfiguration.environment = 'development';
+        testContext.view.model.environment = 'development';
 
         return testContext.view.initialize().then(() => {
           expect(testContext.paypal.Button.render).toBeCalledWith(expect.objectContaining({
@@ -479,16 +480,15 @@ describe('BasePayPalView', () => {
     });
 
     test(
-      'adds `vaulted: true` to the tokenization payload if flow is vault and is not guest checkout',
+      'vaults and adds `vaulted: true` to the tokenization payload if flow is vault and global autovaulting iis enabled',
       done => {
         const paypalInstance = testContext.paypalInstance;
         const model = testContext.model;
         const fakePayload = {
-          foo: 'bar',
-          vaulted: true
+          foo: 'bar'
         };
 
-        model.isGuestCheckout = false;
+        model.vaultManagerConfig.autoVaultPaymentMethods = true;
 
         paypalInstance.tokenizePayment.mockResolvedValue(fakePayload);
         paypalInstance.paypalConfiguration = { flow: 'vault' };
@@ -499,17 +499,23 @@ describe('BasePayPalView', () => {
         testContext.view.initialize().then(() => {
           const onAuthFunction = testContext.paypal.Button.render.mock.calls[0][0].onAuthorize;
           const tokenizeOptions = {
-            foo: 'bar'
+            data: 'bar'
           };
 
           onAuthFunction(tokenizeOptions);
 
           expect(paypalInstance.tokenizePayment).toBeCalledTimes(1);
-          expect(paypalInstance.tokenizePayment).toBeCalledWith(tokenizeOptions);
+          expect(paypalInstance.tokenizePayment).toBeCalledWith({
+            data: 'bar',
+            vault: true
+          });
 
           setTimeout(() => {
             expect(model.addPaymentMethod).toBeCalledTimes(1);
-            expect(model.addPaymentMethod).toBeCalledWith(fakePayload);
+            expect(model.addPaymentMethod).toBeCalledWith({
+              foo: 'bar',
+              vaulted: true
+            });
 
             done();
           }, 1);
@@ -518,7 +524,99 @@ describe('BasePayPalView', () => {
     );
 
     test(
-      'does not add `vaulted: true` to the tokenization payload if flow is vault but is guest checkout',
+      'vaults and adds `vaulted: true` to the tokenization payload if flow is vault and global autovaulting is not enabled but local autovaulting is',
+      done => {
+        const paypalInstance = testContext.paypalInstance;
+        const model = testContext.model;
+        const fakePayload = {
+          foo: 'bar'
+        };
+
+        model.vaultManagerConfig.autoVaultPaymentMethods = false;
+
+        paypalInstance.tokenizePayment.mockResolvedValue(fakePayload);
+        paypalInstance.paypalConfiguration = {
+          flow: 'vault'
+        };
+        jest.spyOn(model, 'addPaymentMethod').mockImplementation();
+
+        testContext.paypal.Button.render.mockResolvedValue();
+
+        model.merchantConfiguration.paypal.vault = {
+          autoVault: true
+        };
+        testContext.view.initialize().then(() => {
+          const onAuthFunction = testContext.paypal.Button.render.mock.calls[0][0].onAuthorize;
+          const tokenizeOptions = {
+            data: 'bar'
+          };
+
+          onAuthFunction(tokenizeOptions);
+
+          expect(paypalInstance.tokenizePayment).toBeCalledTimes(1);
+          expect(paypalInstance.tokenizePayment).toBeCalledWith({
+            data: 'bar',
+            vault: true
+          });
+
+          setTimeout(() => {
+            expect(model.addPaymentMethod).toBeCalledTimes(1);
+            expect(model.addPaymentMethod).toBeCalledWith({
+              foo: 'bar',
+              vaulted: true
+            });
+
+            done();
+          }, 1);
+        });
+      }
+    );
+
+    test(
+      'does not add `vaulted: true` to the tokenization payload if flow is vault but global auto-vaulting is not enabled',
+      done => {
+        const paypalInstance = testContext.paypalInstance;
+        const model = testContext.model;
+        const fakePayload = {
+          foo: 'bar'
+        };
+
+        model.vaultManagerConfig.autoVaultPaymentMethods = false;
+
+        paypalInstance.tokenizePayment.mockResolvedValue(fakePayload);
+        paypalInstance.paypalConfiguration = { flow: 'vault' };
+        jest.spyOn(model, 'addPaymentMethod').mockImplementation();
+
+        testContext.paypal.Button.render.mockResolvedValue();
+
+        testContext.view.initialize().then(() => {
+          const onAuthFunction = testContext.paypal.Button.render.mock.calls[0][0].onAuthorize;
+          const tokenizeOptions = {
+            data: 'bar'
+          };
+
+          onAuthFunction(tokenizeOptions);
+
+          expect(paypalInstance.tokenizePayment).toBeCalledTimes(1);
+          expect(paypalInstance.tokenizePayment).toBeCalledWith({
+            data: 'bar',
+            vault: false
+          });
+
+          setTimeout(() => {
+            expect(model.addPaymentMethod).toBeCalledTimes(1);
+            expect(model.addPaymentMethod).toBeCalledWith({
+              foo: 'bar'
+            });
+
+            done();
+          }, 100);
+        });
+      }
+    );
+
+    test(
+      'does not add `vaulted: true` to the tokenization payload if flow is vault and global auto-vaulting is enabled enabled but local autoVault setting is false',
       done => {
         const paypalInstance = testContext.paypalInstance;
         const model = testContext.model;
@@ -526,7 +624,10 @@ describe('BasePayPalView', () => {
           foo: 'bar'
         };
 
-        model.isGuestCheckout = true;
+        model.vaultManagerConfig.autoVaultPaymentMethods = true;
+        model.merchantConfiguration.paypal.vault = {
+          autoVault: false
+        };
 
         paypalInstance.tokenizePayment.mockResolvedValue(fakePayload);
         paypalInstance.paypalConfiguration = { flow: 'vault' };
@@ -537,13 +638,16 @@ describe('BasePayPalView', () => {
         testContext.view.initialize().then(() => {
           const onAuthFunction = testContext.paypal.Button.render.mock.calls[0][0].onAuthorize;
           const tokenizeOptions = {
-            foo: 'bar'
+            data: 'bar'
           };
 
           onAuthFunction(tokenizeOptions);
 
           expect(paypalInstance.tokenizePayment).toBeCalledTimes(1);
-          expect(paypalInstance.tokenizePayment).toBeCalledWith(tokenizeOptions);
+          expect(paypalInstance.tokenizePayment).toBeCalledWith({
+            data: 'bar',
+            vault: false
+          });
 
           setTimeout(() => {
             expect(model.addPaymentMethod).toBeCalledTimes(1);
@@ -556,7 +660,7 @@ describe('BasePayPalView', () => {
     );
 
     test(
-      'does not add `vaulted: true` to the tokenization payload if flow is checkout and is not guest checkout',
+      'does not add `vaulted: true` to the tokenization payload if flow is checkout',
       done => {
         const paypalInstance = testContext.paypalInstance;
         const model = testContext.model;
@@ -564,10 +668,10 @@ describe('BasePayPalView', () => {
           foo: 'bar'
         };
 
-        model.isGuestCheckout = false;
+        model.vaultManagerConfig.autoVaultPaymentMethods = true;
 
         paypalInstance.tokenizePayment.mockResolvedValue(fakePayload);
-        paypalInstance.paypalConfiguration = { flow: 'checkout' };
+        model.merchantConfiguration.paypal = { flow: 'checkout' };
         jest.spyOn(model, 'addPaymentMethod').mockImplementation();
 
         testContext.paypal.Button.render.mockResolvedValue();
@@ -575,13 +679,16 @@ describe('BasePayPalView', () => {
         testContext.view.initialize().then(() => {
           const onAuthFunction = testContext.paypal.Button.render.mock.calls[0][0].onAuthorize;
           const tokenizeOptions = {
-            foo: 'bar'
+            data: 'bar'
           };
 
           onAuthFunction(tokenizeOptions);
 
           expect(paypalInstance.tokenizePayment).toBeCalledTimes(1);
-          expect(paypalInstance.tokenizePayment).toBeCalledWith(tokenizeOptions);
+          expect(paypalInstance.tokenizePayment).toBeCalledWith({
+            data: 'bar',
+            vault: false
+          });
 
           setTimeout(() => {
             expect(model.addPaymentMethod).toBeCalledTimes(1);
@@ -880,12 +987,37 @@ describe('BasePayPalView', () => {
         currency: 'USD'
       });
     });
+
+    test('can set properties on vault config', () => {
+      const view = new BasePayPalView();
+
+      view.paypalConfiguration = {
+        flow: 'vault',
+        amount: '10.00'
+      };
+      view.vaultConfig = {
+        autoVault: true
+      };
+
+      view.updateConfiguration('vault', {
+        autoVault: false,
+        foo: 'bar'
+      });
+
+      expect(view.vaultConfig).toEqual({
+        autoVault: false,
+        foo: 'bar'
+      });
+      expect(view.paypalConfiguration).toEqual({
+        flow: 'vault',
+        amount: '10.00'
+      });
+    });
   });
 
   describe('isEnabled', () => {
     beforeEach(() => {
       testContext.options = {
-        client: testContext.fakeClient,
         merchantConfiguration: {
           paypal: {}
         }
@@ -899,17 +1031,6 @@ describe('BasePayPalView', () => {
       jest.spyOn(assets, 'loadScript').mockResolvedValue();
     });
 
-    test(
-      'resolves false if merchant does not have PayPal enabled on the gateway',
-      () => {
-        testContext.configuration.gatewayConfiguration.paypalEnabled = false;
-
-        return BasePayPalView.isEnabled(testContext.options).then(result => {
-          expect(result).toBe(false);
-        });
-      }
-    );
-
     test('resolves false if browser is IE9', () => {
       jest.spyOn(browserDetection, 'isIe9').mockReturnValue(true);
 
diff --git a/test/unit/views/payment-sheet-views/card-view.js b/test/unit/views/payment-sheet-views/card-view.js
index a44b0d6..12c9580 100644
--- a/test/unit/views/payment-sheet-views/card-view.js
+++ b/test/unit/views/payment-sheet-views/card-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../../src/lib/analytics');
 
 const BaseView = require('../../../../src/views/base-view');
 const CardView = require('../../../../src/views/payment-sheet-views/card-view');
@@ -26,8 +27,6 @@ describe('CardView', () => {
     testContext.div.innerHTML = mainHTML;
     document.body.appendChild(testContext.div);
     testContext.element = document.body.querySelector('.braintree-sheet.braintree-card');
-
-    testContext.client = fake.client();
   });
 
   afterEach(() => {
@@ -42,9 +41,8 @@ describe('CardView', () => {
 
   describe('initialize', () => {
     beforeEach(() => {
-      testContext.hostedFieldsInstance = {
-        on: jest.fn()
-      };
+      testContext.hostedFieldsInstance = fake.hostedFieldsInstance;
+      fake.hostedFieldsInstance.getSupportedCardTypes.mockResolvedValue([]);
       jest.spyOn(hostedFields, 'create').mockResolvedValue(testContext.hostedFieldsInstance);
 
       testContext.model = fake.model();
@@ -52,146 +50,68 @@ describe('CardView', () => {
       return testContext.model.initialize();
     });
 
-    test('has cvv if supplied in challenges', () => {
-      testContext.client.getConfiguration.mockReturnValue({
-        gatewayConfiguration: {
-          challenges: ['cvv'],
-          creditCards: {
-            supportedCardTypes: []
-          }
-        }
-      });
-
+    test('has cvv by default', async () => {
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
-      return testContext.view.initialize().then(() => {
-        expect(testContext.element.querySelector('[data-braintree-id="cvv-field-group"]')).toBeDefined();
-      });
-    });
+      await testContext.view.initialize();
 
-    test(
-      'does not have cvv if supplied in challenges, but hosted fields overrides sets cvv to null',
-      () => {
-        testContext.client.getConfiguration.mockReturnValue({
-          gatewayConfiguration: {
-            challenges: ['cvv'],
-            creditCards: {
-              supportedCardTypes: []
-            }
-          }
-        });
-
-        testContext.model.merchantConfiguration.card = {
-          overrides: {
-            fields: {
-              cvv: null
-            }
-          }
-        };
-
-        testContext.view = new CardView({
-          element: testContext.element,
-          mainView: testContext.mainView,
-          model: testContext.model,
-          client: testContext.client,
-          strings: strings
-        });
+      expect(testContext.element.querySelector('[data-braintree-id="cvv-field-group"]')).toBeTruthy();
+    });
 
-        return testContext.view.initialize().then(() => {
-          expect(testContext.element.querySelector('[data-braintree-id="cvv-field-group"]')).toBeFalsy();
-        });
-      }
-    );
+    test('does not have cvv if merchant passes `card.cvv.collect = false`', async () => {
+      testContext.model.merchantConfiguration.card = {
+        cvv: {
+          collect: false
+        }
+      };
 
-    test('does not have cvv if not supplied in challenges', () => {
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
-      return testContext.view.initialize().then(() => {
-        expect(testContext.element.querySelector('[data-braintree-id="cvv-field-group"]')).toBeFalsy();
-      });
+      await testContext.view.initialize();
+
+      expect(testContext.element.querySelector('[data-braintree-id="cvv-field-group"]')).toBeFalsy();
     });
 
-    test('has postal code if supplied in challenges', () => {
-      testContext.client.getConfiguration.mockReturnValue({
-        gatewayConfiguration: {
-          challenges: ['postal_code'],
-          creditCards: {
-            supportedCardTypes: []
-          }
+    test('has postal code if merchant passes `card.postalCode.collect = true`', async () => {
+      testContext.model.merchantConfiguration.card = {
+        postalCode: {
+          collect: true
         }
-      });
+      };
 
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
-      return testContext.view.initialize().then(() => {
-        expect(testContext.element.querySelector('[data-braintree-id="postal-code-field-group"]')).toBeDefined();
-      });
-    });
-
-    test(
-      'does not have postal code if supplied in challenges, but hosted fields overrides sets postal code to null',
-      () => {
-        testContext.client.getConfiguration.mockReturnValue({
-          gatewayConfiguration: {
-            challenges: ['postal_code'],
-            creditCards: {
-              supportedCardTypes: []
-            }
-          }
-        });
-
-        testContext.model.merchantConfiguration.card = {
-          overrides: {
-            fields: {
-              postalCode: null
-            }
-          }
-        };
-
-        testContext.view = new CardView({
-          element: testContext.element,
-          mainView: testContext.mainView,
-          model: testContext.model,
-          client: testContext.client,
-          strings: strings
-        });
+      await testContext.view.initialize();
 
-        return testContext.view.initialize().then(() => {
-          expect(testContext.element.querySelector('[data-braintree-id="postal-code-field-group"]')).toBeFalsy();
-        });
-      }
-    );
+      expect(testContext.element.querySelector('[data-braintree-id="postal-code-field-group"]')).toBeTruthy();
+    });
 
-    test('does not have postal code if not supplied in challenges', () => {
+    test('does not have postal code by default', async () => {
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
-      return testContext.view.initialize().then(() => {
-        expect(testContext.element.querySelector('[data-braintree-id="postal-code-field-group"]')).toBeFalsy();
-      });
+      await testContext.view.initialize();
+
+      expect(testContext.element.querySelector('[data-braintree-id="postal-code-field-group"]')).toBeFalsy();
     });
 
     test('has cardholderName if provided in merchant configuration', () => {
@@ -203,7 +123,6 @@ describe('CardView', () => {
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -221,7 +140,6 @@ describe('CardView', () => {
           element: testContext.element,
           mainView: testContext.mainView,
           model: testContext.model,
-          client: testContext.client,
           strings: strings
         });
 
@@ -234,16 +152,15 @@ describe('CardView', () => {
     test('removes hidden class from save card input if configured', () => {
       testContext.model.merchantConfiguration.card = {
         vault: {
-          allowVaultCardOverride: true
+          allowAutoVaultOverride: true
         }
       };
-      testContext.model.isGuestCheckout = false;
+      testContext.model.vaultManagerConfig.autoVaultPaymentMethods = true;
 
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -258,13 +175,12 @@ describe('CardView', () => {
       'does not remove hidden class from save card input if not configured',
       () => {
         testContext.model.merchantConfiguration.card = {};
-        testContext.model.isGuestCheckout = false;
+        testContext.model.vaultManagerConfig.autoVaultPaymentMethods = true;
 
         testContext.view = new CardView({
           element: testContext.element,
           mainView: testContext.mainView,
           model: testContext.model,
-          client: testContext.client,
           strings: strings
         });
 
@@ -279,7 +195,7 @@ describe('CardView', () => {
     test('sets checked value for save card input', () => {
       testContext.model.merchantConfiguration.card = {
         vault: {
-          vaultCard: false
+          autoVault: false
         }
       };
 
@@ -287,7 +203,6 @@ describe('CardView', () => {
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -303,7 +218,6 @@ describe('CardView', () => {
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -319,7 +233,6 @@ describe('CardView', () => {
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -328,90 +241,54 @@ describe('CardView', () => {
       });
     });
 
-    test(
-      'notifies async dependency is ready when Hosted Fields is created',
-      () => {
-        jest.spyOn(DropinModel.prototype, 'asyncDependencyReady');
-
-        testContext.view = new CardView({
-          element: testContext.element,
-          mainView: testContext.mainView,
-          model: testContext.model,
-          client: testContext.client,
-          strings: strings
-        });
-
-        return testContext.view.initialize().then(() => {
-          expect(DropinModel.prototype.asyncDependencyReady).toBeCalledTimes(1);
-        });
-      }
-    );
+    test('notifies async dependency is ready when Hosted Fields is created', async () => {
+      jest.spyOn(DropinModel.prototype, 'asyncDependencyReady');
 
-    test('creates Hosted Fields with number and expiration date', () => {
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
-      return testContext.view.initialize().then(() => {
-        expect(hostedFields.create).toBeCalledWith(expect.objectContaining({
-          client: testContext.client,
-          fields: {
-            number: expect.any(Object),
-            expirationDate: expect.any(Object)
-          }
-        }));
-        expect(hostedFields.create.mock.calls[0][0]).not.toHaveProperty('fields.cvv');
-        expect(hostedFields.create.mock.calls[0][0]).not.toHaveProperty('fields.postalCode');
-      });
-    });
+      await testContext.view.initialize();
 
-    test('creates Hosted Fields with cvv if included in challenges', () => {
-      testContext.client.getConfiguration.mockReturnValue({
-        gatewayConfiguration: {
-          challenges: ['cvv'],
-          creditCards: {
-            supportedCardTypes: []
-          }
-        }
-      });
+      expect(DropinModel.prototype.asyncDependencyReady).toBeCalledTimes(1);
+    });
 
+    test('creates Hosted Fields with number, cvv and expiration date', () => {
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
-        strings: strings,
-        merchantConfiguration: {
-          authorization: fake.clientToken
-        }
+        strings: strings
       });
 
       return testContext.view.initialize().then(() => {
-        expect(hostedFields.create.mock.calls[0][0].fields).toHaveProperty('cvv');
+        expect(hostedFields.create).toBeCalledWith(expect.objectContaining({
+          authorization: testContext.view.model.authorization,
+          fields: {
+            number: expect.any(Object),
+            cvv: expect.any(Object),
+            expirationDate: expect.any(Object)
+          }
+        }));
       });
     });
 
     test(
-      'creates Hosted Fields with postal code if included in challenges',
+      'creates Hosted Fields with postal code if included in config',
       () => {
-        testContext.client.getConfiguration.mockReturnValue({
-          gatewayConfiguration: {
-            challenges: ['postal_code'],
-            creditCards: {
-              supportedCardTypes: []
-            }
+        testContext.model.merchantConfiguration.card = {
+          postalCode: {
+            collect: true
           }
-        });
+        };
 
         testContext.view = new CardView({
           element: testContext.element,
           mainView: testContext.mainView,
           model: testContext.model,
-          client: testContext.client,
           strings: strings,
           merchantConfiguration: {
             authorization: fake.clientToken
@@ -438,7 +315,6 @@ describe('CardView', () => {
           element: testContext.element,
           mainView: testContext.mainView,
           model: testContext.model,
-          client: testContext.client,
           strings: strings
         });
 
@@ -451,90 +327,78 @@ describe('CardView', () => {
       }
     );
 
-    test('shows supported card icons', () => {
-      const supportedCardTypes = ['american-express', 'discover', 'jcb', 'master-card', 'visa'];
+    test('shows supported card icons', (done) => {
+      const unsupportedCardTypes = ['maestro', 'diners-club', 'unionpay', 'discover'];
+      const supportedCardTypes = ['visa', 'mastercard', 'american-express', 'jcb'];
+
+      testContext.hostedFieldsInstance.getSupportedCardTypes.mockResolvedValue([
+        'Visa',
+        'Mastercard',
+        'American Express',
+        'JCB'
+      ]);
 
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
-      return testContext.view.initialize().then(() => {
-        supportedCardTypes.forEach(cardType => {
-          const cardIcon = testContext.element.querySelector('[data-braintree-id="' + cardType + '-card-icon"]');
-
-          expect(cardIcon.classList.contains('braintree-hidden')).toBe(false);
-        });
-      });
-    });
-
-    test('hides unsupported card icons', () => {
-      const unsupportedCardTypes = ['maestro', 'diners-club'];
+      testContext.view.initialize().then(() => {
+        setTimeout(() => {
+          unsupportedCardTypes.forEach(cardType => {
+            const cardIcon = testContext.element.querySelector('[data-braintree-id="' + cardType + '-card-icon"]');
 
-      testContext.view = new CardView({
-        element: testContext.element,
-        mainView: testContext.mainView,
-        model: testContext.model,
-        client: testContext.client,
-        strings: strings
-      });
+            expect(cardIcon.classList.contains('braintree-hidden')).toBe(true);
+          });
+          supportedCardTypes.forEach(cardType => {
+            const cardIcon = testContext.element.querySelector('[data-braintree-id="' + cardType + '-card-icon"]');
 
-      return testContext.view.initialize().then(() => {
-        unsupportedCardTypes.forEach(cardType => {
-          const cardIcon = testContext.element.querySelector('[data-braintree-id="' + cardType + '-card-icon"]');
+            expect(cardIcon.classList.contains('braintree-hidden')).toBe(false);
+          });
 
-          expect(cardIcon.classList.contains('braintree-hidden')).toBe(true);
-        });
+          done();
+        }, 1);
       });
     });
 
-    test('does not show UnionPay icon even if it is supported', () => {
-      let unionPayCardIcon;
-
-      testContext.client.getConfiguration.mockReturnValue({
-        gatewayConfiguration: {
-          challenges: [],
-          creditCards: {
-            supportedCardTypes: ['UnionPay']
-          }
-        }
-      });
+    test('does not show UnionPay icon even if it is supported', (done) => {
+      fake.hostedFieldsInstance.getSupportedCardTypes.mockResolvedValue([
+        'UnionPay'
+      ]);
 
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
       return testContext.view.initialize().then(() => {
-        unionPayCardIcon = testContext.element.querySelector('[data-braintree-id="unionpay-card-icon"]');
+        setTimeout(() => {
+          const unionPayCardIcon = testContext.element.querySelector('[data-braintree-id="unionpay-card-icon"]');
+
+          expect(unionPayCardIcon.classList.contains('braintree-hidden')).toBe(true);
 
-        expect(unionPayCardIcon.classList.contains('braintree-hidden')).toBe(true);
+          done();
+        }, 1);
       });
     });
 
     test('sets field placeholders', () => {
       let hostedFieldsConfiguredFields;
 
-      testContext.client.getConfiguration.mockReturnValue({
-        gatewayConfiguration: {
-          challenges: ['cvv', 'postal_code'],
-          creditCards: {
-            supportedCardTypes: []
-          }
+      testContext.model.merchantConfiguration.card = {
+        postalCode: {
+          collect: true
         }
-      });
+      };
 
       testContext.view = new CardView({
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -551,14 +415,6 @@ describe('CardView', () => {
     test('allows overriding field options for hosted fields', () => {
       let hostedFieldsConfiguredFields;
 
-      testContext.client.getConfiguration.mockReturnValue({
-        gatewayConfiguration: {
-          challenges: ['cvv', 'postal_code'],
-          creditCards: {
-            supportedCardTypes: []
-          }
-        }
-      });
       testContext.model.merchantConfiguration.card = {
         overrides: {
           fields: {
@@ -576,7 +432,6 @@ describe('CardView', () => {
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -594,6 +449,9 @@ describe('CardView', () => {
         let hostedFieldsConfiguredFields;
 
         testContext.model.merchantConfiguration.card = {
+          cvv: {
+            collect: false
+          },
           overrides: {
             fields: {
               postalCode: {
@@ -616,7 +474,6 @@ describe('CardView', () => {
           element: testContext.element,
           mainView: testContext.mainView,
           model: testContext.model,
-          client: testContext.client,
           strings: strings
         });
 
@@ -646,7 +503,6 @@ describe('CardView', () => {
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -677,7 +533,6 @@ describe('CardView', () => {
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -713,7 +568,6 @@ describe('CardView', () => {
           element: testContext.element,
           mainView: testContext.mainView,
           model: testContext.model,
-          client: testContext.client,
           strings: strings
         });
 
@@ -733,20 +587,13 @@ describe('CardView', () => {
   describe('isEnabled', () => {
     beforeEach(() => {
       testContext.fakeOptions = {
-        client: testContext.client,
         merchantConfiguration: {}
       };
     });
 
     test(
-      'resovles with true when there is at least one supported card type',
+      'resolves with true when no merchant configuration provided',
       () => {
-        const configuration = fake.configuration();
-
-        configuration.gatewayConfiguration.creditCards.supportedCardTypes = ['visa'];
-
-        testContext.client.getConfiguration.mockReturnValue(configuration);
-
         return CardView.isEnabled(testContext.fakeOptions).then(result => {
           expect(result).toBe(true);
         });
@@ -754,13 +601,8 @@ describe('CardView', () => {
     );
 
     test(
-      'resovles with false when merchant configuration sets card to false',
+      'resolves with false when merchant configuration sets card to false',
       () => {
-        const configuration = fake.configuration();
-
-        configuration.gatewayConfiguration.creditCards.supportedCardTypes = ['visa'];
-
-        testContext.client.getConfiguration.mockReturnValue(configuration);
         testContext.fakeOptions.merchantConfiguration.card = false;
 
         return CardView.isEnabled(testContext.fakeOptions).then(result => {
@@ -768,21 +610,6 @@ describe('CardView', () => {
         });
       }
     );
-
-    test(
-      'resovles with false when there are no supported card types',
-      () => {
-        const configuration = fake.configuration();
-
-        configuration.gatewayConfiguration.creditCards.supportedCardTypes = [];
-
-        testContext.client.getConfiguration.mockReturnValue(configuration);
-
-        return CardView.isEnabled(testContext.fakeOptions).then(result => {
-          expect(result).toBe(false);
-        });
-      }
-    );
   });
 
   describe('requestPaymentMethod', () => {
@@ -799,7 +626,6 @@ describe('CardView', () => {
         element: testContext.element,
         mainView: testContext.mainView,
         model: testContext.model,
-        client: testContext.client,
         strings: strings
       });
 
@@ -818,7 +644,6 @@ describe('CardView', () => {
           element: testContext.element,
           mainView: testContext.mainView,
           model: testContext.model,
-          client: testContext.client,
           strings: strings
         });
 
@@ -846,21 +671,13 @@ describe('CardView', () => {
           hideFieldError: CardView.prototype.hideFieldError,
           showFieldError: CardView.prototype.showFieldError,
           model: model,
-          client: fake.client({
-            gatewayConfiguration: {
-              challenges: ['cvv'],
-              creditCards: {
-                supportedCardTypes: []
-              }
-            }
-          }),
           _shouldVault: CardView.prototype._shouldVault,
           saveCardInput: {
             checked: true
           },
           strings: strings,
           tokenize: CardView.prototype.tokenize,
-          _hideUnsupportedCardIcons: function () {},
+          _showSupportedCardIcons: function () {},
           _isCardTypeSupported: CardView.prototype._isCardTypeSupported,
           _onBlurEvent: function () {},
           _onCardTypeChangeEvent: function () {},
@@ -936,13 +753,14 @@ describe('CardView', () => {
     describe('onBlurEvent', () => {
       beforeEach(() => {
         testContext.context._onBlurEvent = CardView.prototype._onBlurEvent;
+        jest.spyOn(window, 'setTimeout').mockImplementation();
       });
 
       test(
         'removes braintree-form__field-group--is-focused class when blurred',
         () => {
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: { isEmpty: true }
@@ -984,15 +802,6 @@ describe('CardView', () => {
         const numberFieldError = testContext.element.querySelector('[data-braintree-id="number-field-error"]');
         const numberFieldGroup = testContext.element.querySelector('[data-braintree-id="number-field-group"]');
 
-        testContext.context.client.getConfiguration.mockReturnValue({
-          gatewayConfiguration: {
-            challenges: ['cvv'],
-            creditCards: {
-              supportedCardTypes: ['Visa']
-            }
-          }
-        });
-
         classList.remove(numberFieldGroup, 'braintree-form__field-group--has-error');
         jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
@@ -1007,7 +816,7 @@ describe('CardView', () => {
         () => {
           const fakeHostedField = document.createElement('iframe');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1028,25 +837,15 @@ describe('CardView', () => {
           document.body.appendChild(fakeHostedField);
           fakeHostedField.focus();
 
-          testContext.context.client.getConfiguration.mockReturnValue({
-            authorization: fake.clientToken,
-            authorizationType: 'CLIENT_TOKEN',
-            gatewayConfiguration: {
-              challenges: ['cvv'],
-              creditCards: {
-                supportedCardTypes: ['Visa']
-              }
-            }
-          });
-
-          modelOptions.client.getConfiguration = testContext.context.client.getConfiguration;
-
+          modelOptions.merchantConfiguration.authorization = fake.clientToken;
           testContext.context.model = fake.model(modelOptions);
 
           classList.remove(numberFieldGroup, 'braintree-form__field-group--has-error');
           jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
-          return CardView.prototype.initialize.call(testContext.context).then(() => {
+          return testContext.context.model.initialize().then(() => {
+            return CardView.prototype.initialize.call(testContext.context);
+          }).then(() => {
             expect(numberFieldGroup.classList.contains('braintree-form__field-group--has-error')).toBe(true);
             expect(numberFieldError.textContent).toBe('Please fill out a card number.');
           });
@@ -1056,10 +855,12 @@ describe('CardView', () => {
       test(
         'sets the empty error when programatically focussing a hosted field (requires a setTimeout)',
         done => {
+          window.setTimeout.mockRestore();
+
           const fakeElement = document.createElement('div');
           const fakeHostedField = document.createElement('iframe');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1081,25 +882,15 @@ describe('CardView', () => {
           document.body.appendChild(fakeHostedField);
           fakeElement.focus();
 
-          testContext.context.client.getConfiguration.mockReturnValue({
-            authorization: fake.clientToken,
-            authorizationType: 'CLIENT_TOKEN',
-            gatewayConfiguration: {
-              challenges: ['cvv'],
-              creditCards: {
-                supportedCardTypes: ['Visa']
-              }
-            }
-          });
-
-          modelOptions.client.getConfiguration = testContext.context.client.getConfiguration;
-
+          modelOptions.merchantConfiguration.authorization = fake.clientToken;
           testContext.context.model = fake.model(modelOptions);
 
           classList.remove(numberFieldGroup, 'braintree-form__field-group--has-error');
           jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
-          CardView.prototype.initialize.call(testContext.context).then(() => {
+          return testContext.context.model.initialize().then(() => {
+            return CardView.prototype.initialize.call(testContext.context);
+          }).then(() => {
             expect(numberFieldGroup.classList.contains('braintree-form__field-group--has-error')).toBe(false);
 
             fakeHostedField.focus();
@@ -1117,7 +908,7 @@ describe('CardView', () => {
         () => {
           const fakeElement = document.createElement('iframe');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1135,25 +926,15 @@ describe('CardView', () => {
           document.body.appendChild(fakeElement);
           fakeElement.focus();
 
-          testContext.context.client.getConfiguration.mockReturnValue({
-            authorization: fake.clientToken,
-            authorizationType: 'CLIENT_TOKEN',
-            gatewayConfiguration: {
-              challenges: ['cvv'],
-              creditCards: {
-                supportedCardTypes: ['Visa']
-              }
-            }
-          });
-
-          modelOptions.client.getConfiguration = testContext.context.client.getConfiguration;
-
+          modelOptions.merchantConfiguration.authorization = fake.clientToken;
           testContext.context.model = fake.model(modelOptions);
 
           classList.remove(numberFieldGroup, 'braintree-form__field-group--has-error');
           jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
-          return CardView.prototype.initialize.call(testContext.context).then(() => {
+          return testContext.context.model.initialize().then(() => {
+            return CardView.prototype.initialize.call(testContext.context);
+          }).then(() => {
             expect(numberFieldGroup.classList.contains('braintree-form__field-group--has-error')).toBe(false);
           });
         }
@@ -1164,7 +945,7 @@ describe('CardView', () => {
         () => {
           const fakeElement = document.createElement('div');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1182,25 +963,15 @@ describe('CardView', () => {
           document.body.appendChild(fakeElement);
           fakeElement.focus();
 
-          testContext.context.client.getConfiguration.mockReturnValue({
-            authorization: fake.clientToken,
-            authorizationType: 'CLIENT_TOKEN',
-            gatewayConfiguration: {
-              challenges: ['cvv'],
-              creditCards: {
-                supportedCardTypes: ['Visa']
-              }
-            }
-          });
-
-          modelOptions.client.getConfiguration = testContext.context.client.getConfiguration;
-
+          modelOptions.merchantConfiguration.authorization = fake.clientToken;
           testContext.context.model = fake.model(modelOptions);
 
           classList.remove(numberFieldGroup, 'braintree-form__field-group--has-error');
           jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
-          return CardView.prototype.initialize.call(testContext.context).then(() => {
+          return testContext.context.model.initialize().then(() => {
+            return CardView.prototype.initialize.call(testContext.context);
+          }).then(() => {
             expect(numberFieldGroup.classList.contains('braintree-form__field-group--has-error')).toBe(false);
           });
         }
@@ -1217,7 +988,7 @@ describe('CardView', () => {
         () => {
           const numberFieldGroup = testContext.element.querySelector('[data-braintree-id="number-field-group"]');
           const fakeEvent = {
-            cards: [{ type: 'master-card' }],
+            cards: [{ type: 'master-card', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1261,7 +1032,7 @@ describe('CardView', () => {
         () => {
           const numberFieldGroup = testContext.element.querySelector('[data-braintree-id="number-field-group"]');
           const fakeEvent = {
-            cards: [{ type: 'master-card' }, { type: 'foo-pay' }],
+            cards: [{ type: 'master-card', supported: true }, { type: 'foo-pay', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1284,7 +1055,7 @@ describe('CardView', () => {
         () => {
           const cardNumberIcon = testContext.element.querySelector('[data-braintree-id="card-number-icon"]');
           const fakeEvent = {
-            cards: [{ type: 'master-card' }],
+            cards: [{ type: 'master-card', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1305,7 +1076,7 @@ describe('CardView', () => {
         () => {
           const cardNumberIcon = testContext.element.querySelector('[data-braintree-id="card-number-icon"]');
           const fakeEvent = {
-            cards: [{ type: 'master-card' }, { type: 'foo-pay' }],
+            cards: [{ type: 'master-card', supported: true }, { type: 'foo-pay', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1345,7 +1116,7 @@ describe('CardView', () => {
       test('updates the cvv icon to back icon for non-amex cards', () => {
         const use = testContext.element.querySelector('[data-braintree-id="cvv-icon"]').querySelector('use');
         const fakeEvent = {
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           emittedBy: 'number'
         };
         const hostedFieldsInstance = {
@@ -1364,7 +1135,7 @@ describe('CardView', () => {
       test('updates the cvv icon to front icon for amex cards', () => {
         const use = testContext.element.querySelector('[data-braintree-id="cvv-icon"]').querySelector('use');
         const fakeEvent = {
-          cards: [{ type: 'american-express' }],
+          cards: [{ type: 'american-express', supported: true }],
           emittedBy: 'number'
         };
         const hostedFieldsInstance = {
@@ -1384,7 +1155,7 @@ describe('CardView', () => {
         () => {
           const cvvLabelDescriptor = testContext.element.querySelector('[data-braintree-id="cvv-field-group"]').querySelector('.braintree-form__descriptor');
           const fakeEvent = {
-            cards: [{ type: 'american-express' }],
+            cards: [{ type: 'american-express', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1406,7 +1177,7 @@ describe('CardView', () => {
         () => {
           const cvvLabelDescriptor = testContext.element.querySelector('[data-braintree-id="cvv-field-group"]').querySelector('.braintree-form__descriptor');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1428,7 +1199,7 @@ describe('CardView', () => {
         () => {
           const cvvLabelDescriptor = testContext.element.querySelector('[data-braintree-id="cvv-field-group"]').querySelector('.braintree-form__descriptor');
           const fakeEvent = {
-            cards: [{ type: 'american-express' }, { type: 'visa' }],
+            cards: [{ type: 'american-express', supported: true }, { type: 'visa', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1447,7 +1218,7 @@ describe('CardView', () => {
 
       test('updates the cvv field placeholder when card type is amex', () => {
         const fakeEvent = {
-          cards: [{ type: 'american-express' }],
+          cards: [{ type: 'american-express', supported: true }],
           emittedBy: 'number'
         };
         const hostedFieldsInstance = {
@@ -1466,7 +1237,7 @@ describe('CardView', () => {
         'updates the cvv field placeholder when card type is non-amex',
         () => {
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1486,7 +1257,7 @@ describe('CardView', () => {
         'updates the cvv field placeholder when multiple card types',
         () => {
           const fakeEvent = {
-            cards: [{ type: 'american-express' }, { type: 'visa' }],
+            cards: [{ type: 'american-express', supported: true }, { type: 'visa', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1506,7 +1277,7 @@ describe('CardView', () => {
         'does not update the cvv field placeholder when there is no cvv challenge',
         () => {
           const fakeEvent = {
-            cards: [{ type: 'american-express' }, { type: 'visa' }],
+            cards: [{ type: 'american-express', supported: true }, { type: 'visa', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1514,14 +1285,11 @@ describe('CardView', () => {
             setAttribute: jest.fn()
           };
 
-          testContext.context.client.getConfiguration.mockReturnValue({
-            gatewayConfiguration: {
-              challenges: [],
-              creditCards: {
-                supportedCardTypes: []
-              }
+          testContext.context.model.merchantConfiguration.card = {
+            cvv: {
+              collect: false
             }
-          });
+          };
 
           jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
@@ -1535,7 +1303,7 @@ describe('CardView', () => {
         'does not update the cvv field placeholder when it is removed with an override',
         () => {
           const fakeEvent = {
-            cards: [{ type: 'american-express' }, { type: 'visa' }],
+            cards: [{ type: 'american-express', supported: true }, { type: 'visa', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1563,7 +1331,7 @@ describe('CardView', () => {
         'does not update the cvv field placeholder when using a custom CVV placeholder',
         () => {
           const fakeEvent = {
-            cards: [{ type: 'american-express' }, { type: 'visa' }],
+            cards: [{ type: 'american-express', supported: true }, { type: 'visa', supported: true }],
             emittedBy: 'number'
           };
           const hostedFieldsInstance = {
@@ -1600,7 +1368,7 @@ describe('CardView', () => {
         () => {
           const fakeEvent = {
             emittedBy: 'number',
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             fields: {
               number: {
                 container: document.createElement('div'),
@@ -1630,7 +1398,7 @@ describe('CardView', () => {
         () => {
           const expirationElement = testContext.element.querySelector('.braintree-form-expiration');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'expirationDate',
             fields: {
               expirationDate: {
@@ -1662,7 +1430,7 @@ describe('CardView', () => {
         () => {
           const expirationElement = testContext.element.querySelector('.braintree-form-expiration');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'expirationDate',
             fields: {
               expirationDate: {
@@ -1694,7 +1462,7 @@ describe('CardView', () => {
         () => {
           const numberElement = testContext.element.querySelector('.braintree-form-number');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1713,15 +1481,6 @@ describe('CardView', () => {
             removeAttribute: jest.fn()
           };
 
-          testContext.context.client.getConfiguration.mockReturnValue({
-            gatewayConfiguration: {
-              challenges: ['cvv'],
-              creditCards: {
-                supportedCardTypes: ['Visa']
-              }
-            }
-          });
-
           jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
           return CardView.prototype.initialize.call(testContext.context).then(() => {
@@ -1735,12 +1494,12 @@ describe('CardView', () => {
         () => {
           const numberElement = testContext.element.querySelector('.braintree-form-number');
           const fakeEvent = {
-            cards: [{ type: 'foo' }],
+            cards: [{ type: 'foo', supported: false }],
             emittedBy: 'number',
             fields: {
               number: {
                 container: numberElement,
-                isValid: true,
+                isValid: false,
                 isPotentiallyValid: true
               }
             }
@@ -1754,15 +1513,6 @@ describe('CardView', () => {
             removeAttribute: jest.fn()
           };
 
-          testContext.context.client.getConfiguration.mockReturnValue({
-            gatewayConfiguration: {
-              challenges: ['cvv'],
-              creditCards: {
-                supportedCardTypes: ['Visa']
-              }
-            }
-          });
-
           jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
           return CardView.prototype.initialize.call(testContext.context).then(() => {
@@ -1776,7 +1526,7 @@ describe('CardView', () => {
         () => {
           const numberElement = testContext.element.querySelector('.braintree-form-number');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1795,15 +1545,6 @@ describe('CardView', () => {
             removeAttribute: jest.fn()
           };
 
-          testContext.context.client.getConfiguration.mockReturnValue({
-            gatewayConfiguration: {
-              challenges: ['cvv'],
-              creditCards: {
-                supportedCardTypes: ['Visa']
-              }
-            }
-          });
-
           jest.spyOn(hostedFields, 'create').mockResolvedValue(hostedFieldsInstance);
 
           return CardView.prototype.initialize.call(testContext.context).then(() => {
@@ -1817,7 +1558,7 @@ describe('CardView', () => {
         () => {
           const numberElement = testContext.element.querySelector('.braintree-form-number');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1847,7 +1588,7 @@ describe('CardView', () => {
         () => {
           const numberElement = testContext.element.querySelector('.braintree-form-number');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1877,7 +1618,7 @@ describe('CardView', () => {
         () => {
           const numberElement = testContext.element.querySelector('.braintree-form-number');
           const fakeEvent = {
-            cards: [{ type: 'visa' }],
+            cards: [{ type: 'visa', supported: true }],
             emittedBy: 'number',
             fields: {
               number: {
@@ -1909,7 +1650,7 @@ describe('CardView', () => {
       test('removes the braintree-form__field-group--has-error class', () => {
         const fakeEvent = {
           emittedBy: 'number',
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               container: document.createElement('div'),
@@ -1937,24 +1678,18 @@ describe('CardView', () => {
 
   describe('tokenize', () => {
     beforeEach(() => {
-      testContext.fakeHostedFieldsInstance = {
-        clear: jest.fn(),
-        getState: jest.fn().mockReturnValue({
-          cards: [{ type: 'visa' }],
-          fields: {
-            number: {
-              isValid: true
-            },
-            expirationDate: {
-              isValid: true
-            }
+      fake.hostedFieldsInstance.getState.mockReturnValue({
+        cards: [{ type: 'visa', supported: true }],
+        fields: {
+          number: {
+            isValid: true
+          },
+          expirationDate: {
+            isValid: true
           }
-        }),
-        removeAttribute: jest.fn(),
-        setAttribute: jest.fn(),
-        setMessage: jest.fn(),
-        tokenize: jest.fn().mockResolvedValue({})
-      };
+        }
+      });
+      fake.hostedFieldsInstance.tokenize.mockResolvedValue({});
       testContext.model = fake.model();
 
       return testContext.model.initialize().then(() => {
@@ -1965,7 +1700,7 @@ describe('CardView', () => {
             checked: true
           },
           getElementById: BaseView.prototype.getElementById,
-          hostedFieldsInstance: testContext.fakeHostedFieldsInstance,
+          hostedFieldsInstance: fake.hostedFieldsInstance,
           fieldErrors: {},
           model: testContext.model,
           preventUserAction: BaseView.prototype.preventUserAction,
@@ -1974,8 +1709,8 @@ describe('CardView', () => {
           _validateExtraInput: CardView.prototype._validateExtraInput,
           _sendRequestableEvent: CardView.prototype._sendRequestableEvent,
           _setupCardholderName: jest.fn(),
-          client: fake.client(),
           merchantConfiguration: {
+            vault: {},
             authorization: fake.configuration().authorization,
             card: {}
           },
@@ -1990,7 +1725,7 @@ describe('CardView', () => {
     test('clears the error on the model', () => {
       jest.spyOn(testContext.model, 'clearError').mockImplementation();
       testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-        cards: [{ type: 'Card' }],
+        cards: [{ type: 'Card', supported: true }],
         fields: {
           number: {
             isValid: true
@@ -2008,7 +1743,7 @@ describe('CardView', () => {
 
     test('throws an error if there is no valid card type', () => {
       testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-        cards: [{ type: 'Card' }],
+        cards: [{ type: 'Card', supported: true }],
         fields: {
           number: {
             isValid: true
@@ -2021,7 +1756,7 @@ describe('CardView', () => {
 
       return CardView.prototype.tokenize.call(testContext.context).then(throwIfResolves).catch(err => {
         expect(err).toBeDefined();
-        expect(testContext.fakeHostedFieldsInstance.tokenize).not.toBeCalled();
+        expect(fake.hostedFieldsInstance.tokenize).not.toBeCalled();
       });
     });
 
@@ -2029,7 +1764,7 @@ describe('CardView', () => {
       'calls callback with error and reports error to DropinModel if form is not valid',
       () => {
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isValid: true
@@ -2043,7 +1778,7 @@ describe('CardView', () => {
         jest.spyOn(testContext.context.model, 'reportError').mockImplementation();
 
         return CardView.prototype.tokenize.call(testContext.context).then(throwIfResolves).catch(err => {
-          expect(testContext.fakeHostedFieldsInstance.tokenize).not.toBeCalled();
+          expect(fake.hostedFieldsInstance.tokenize).not.toBeCalled();
           expect(testContext.context.model.reportError).toBeCalledWith('hostedFieldsFieldsInvalidError');
           expect(err.message).toBe('No payment method is available.');
         });
@@ -2054,7 +1789,7 @@ describe('CardView', () => {
       'calls callback with error when cardholder name is required and the input is empty',
       () => {
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isValid: true
@@ -2078,7 +1813,7 @@ describe('CardView', () => {
         jest.spyOn(testContext.context.model, 'reportError').mockImplementation();
 
         return CardView.prototype.tokenize.call(testContext.context).then(throwIfResolves).catch(err => {
-          expect(testContext.fakeHostedFieldsInstance.tokenize).not.toBeCalled();
+          expect(fake.hostedFieldsInstance.tokenize).not.toBeCalled();
           expect(testContext.context.model.reportError).toBeCalledWith('hostedFieldsFieldsInvalidError');
           expect(err.message).toBe('No payment method is available.');
         });
@@ -2089,7 +1824,7 @@ describe('CardView', () => {
       'does not error if cardholder name is empty, but not required',
       () => {
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isValid: true
@@ -2111,14 +1846,14 @@ describe('CardView', () => {
 
         return CardView.prototype.tokenize.call(testContext.context).then(() => {
           expect(testContext.context.model.reportError).not.toBeCalled();
-          expect(testContext.fakeHostedFieldsInstance.tokenize).toBeCalledTimes(1);
+          expect(fake.hostedFieldsInstance.tokenize).toBeCalledTimes(1);
         });
       }
     );
 
     test('does not error if cardholder name is not included', () => {
       testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-        cards: [{ type: 'visa' }],
+        cards: [{ type: 'visa', supported: true }],
         fields: {
           number: {
             isValid: true
@@ -2133,7 +1868,7 @@ describe('CardView', () => {
 
       return CardView.prototype.tokenize.call(testContext.context).then(() => {
         expect(testContext.context.model.reportError).not.toBeCalled();
-        expect(testContext.fakeHostedFieldsInstance.tokenize).toBeCalledTimes(1);
+        expect(fake.hostedFieldsInstance.tokenize).toBeCalledTimes(1);
       });
     });
 
@@ -2143,7 +1878,7 @@ describe('CardView', () => {
         const overLengthValue = Array(256).join('a');
 
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isValid: true
@@ -2176,7 +1911,7 @@ describe('CardView', () => {
         jest.spyOn(testContext.context.model, 'reportError').mockImplementation();
 
         return CardView.prototype.tokenize.call(testContext.context).then(throwIfResolves).catch(err => {
-          expect(testContext.fakeHostedFieldsInstance.tokenize).not.toBeCalled();
+          expect(fake.hostedFieldsInstance.tokenize).not.toBeCalled();
           expect(testContext.context.model.reportError).toBeCalledWith('hostedFieldsFieldsInvalidError');
           expect(err.message).toBe('No payment method is available.');
         });
@@ -2219,11 +1954,15 @@ describe('CardView', () => {
         const numberFieldError = testContext.element.querySelector('[data-braintree-id="number-field-error"]');
 
         jest.spyOn(testContext.context.model, 'reportError').mockImplementation();
-
-        testContext.context.client.getConfiguration.mockReturnValue({
-          gatewayConfiguration: {
-            creditCards: {
-              supportedCardTypes: ['Foo Pay']
+        testContext.context.hostedFieldsInstance.getState.mockReturnValue({
+          cards: [{ type: 'foo', supported: false }],
+          fields: {
+            number: {
+              isEmpty: false,
+              isValid: false
+            },
+            expirationDate: {
+              isValid: true
             }
           }
         });
@@ -2243,7 +1982,7 @@ describe('CardView', () => {
         const numberFieldError = testContext.element.querySelector('[data-braintree-id="number-field-error"]');
 
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isEmpty: true,
@@ -2269,7 +2008,7 @@ describe('CardView', () => {
         const numberFieldError = testContext.element.querySelector('[data-braintree-id="number-field-error"]');
 
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isValid: false
@@ -2292,7 +2031,7 @@ describe('CardView', () => {
       'sets the aria-invalid attribute and set message when a field error is shown',
       () => {
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isValid: false
@@ -2347,7 +2086,7 @@ describe('CardView', () => {
       'removes the aria-invalid attribute and message when a field error is hidden',
       () => {
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isValid: false
@@ -2406,7 +2145,7 @@ describe('CardView', () => {
     test(
       'includes `vaulted: true` in tokenization payload if not guest checkout',
       () => {
-        testContext.context.model.isGuestCheckout = false;
+        testContext.context.model.vaultManagerConfig.autoVaultPaymentMethods = true;
 
         return CardView.prototype.tokenize.call(testContext.context).then(payload => {
           expect(payload.vaulted).toBe(true);
@@ -2417,7 +2156,7 @@ describe('CardView', () => {
     test(
       'does not include `vaulted: true` in tokenization payload if save card input is not checked',
       () => {
-        testContext.context.model.isGuestCheckout = false;
+        testContext.context.model.vaultManagerConfig.autoVaultPaymentMethods = true;
         testContext.context.saveCardInput.checked = false;
 
         return CardView.prototype.tokenize.call(testContext.context).then(payload => {
@@ -2429,7 +2168,7 @@ describe('CardView', () => {
     test(
       'does not include `vaulted: true` in tokenization payload if guest checkout',
       () => {
-        testContext.context.model.isGuestCheckout = true;
+        testContext.context.model.vaultManagerConfig.autoVaultPaymentMethods = false;
 
         return CardView.prototype.tokenize.call(testContext.context).then(payload => {
           expect(payload.vaulted).toBeFalsy();
@@ -2447,7 +2186,7 @@ describe('CardView', () => {
       'does not call hostedFieldsInstance.tokenize if form is invalid',
       () => {
         testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'visa' }],
+          cards: [{ type: 'visa', supported: true }],
           fields: {
             number: {
               isValid: true
@@ -2464,50 +2203,26 @@ describe('CardView', () => {
       }
     );
 
-    test(
-      'does not call hostedFieldsInstance.tokenize if form is valid, but card type is not supported',
-      () => {
-        testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [{ type: 'foo' }],
-          fields: {
-            number: {
-              isValid: true
-            },
-            expirationDate: {
-              isValid: true
-            }
-          }
-        });
+    test('vaults on tokenization if auto vaulting is configured', () => {
+      testContext.context.model.vaultManagerConfig.autoVaultPaymentMethods = true;
 
-        return CardView.prototype.tokenize.call(testContext.context).then(throwIfResolves).catch(() => {
-          expect(testContext.context.hostedFieldsInstance.tokenize).not.toBeCalled();
-        });
-      }
-    );
+      return CardView.prototype.tokenize.call(testContext.context).then(() => {
+        expect(testContext.context.hostedFieldsInstance.tokenize).toBeCalledWith({ vault: true });
+      });
+    });
 
-    test(
-      'does not call hostedFieldsInstance.tokenize if form is valid, but no card is available in state',
-      () => {
-        testContext.context.hostedFieldsInstance.getState.mockReturnValue({
-          cards: [],
-          fields: {
-            number: {
-              isValid: true
-            },
-            expirationDate: {
-              isValid: true
-            }
-          }
-        });
+    test('does not vault on tokenization if auto vaulting is configured globally, but set to not vault locally', () => {
+      testContext.context.merchantConfiguration.vault.autoVault = false;
+      testContext.context.model.vaultManagerConfig.autoVaultPaymentMethods = true;
 
-        return CardView.prototype.tokenize.call(testContext.context).then(throwIfResolves).catch(() => {
-          expect(testContext.context.hostedFieldsInstance.tokenize).not.toBeCalled();
-        });
-      }
-    );
+      return CardView.prototype.tokenize.call(testContext.context).then(() => {
+        expect(testContext.context.hostedFieldsInstance.tokenize).toBeCalledWith({ vault: false });
+      });
+    });
 
-    test('vaults on tokenization if not using guest checkout', () => {
-      testContext.context.model.isGuestCheckout = false;
+    test('vaults on tokenization if auto vaulting is configured globally to not vault, but set to vault locally', () => {
+      testContext.context.merchantConfiguration.vault.autoVault = true;
+      testContext.context.model.vaultManagerConfig.autoVaultPaymentMethods = false;
 
       return CardView.prototype.tokenize.call(testContext.context).then(() => {
         expect(testContext.context.hostedFieldsInstance.tokenize).toBeCalledWith({ vault: true });
@@ -2517,7 +2232,19 @@ describe('CardView', () => {
     test(
       'does not vault on tokenization if save card input is not checked',
       () => {
-        testContext.context.model.isGuestCheckout = false;
+        testContext.context.model.vaultManagerConfig.autoVaultPaymentMethods = true;
+        testContext.context.saveCardInput.checked = false;
+
+        return CardView.prototype.tokenize.call(testContext.context).then(() => {
+          expect(testContext.context.hostedFieldsInstance.tokenize).toBeCalledWith({ vault: false });
+        });
+      }
+    );
+
+    test(
+      'does not vault on tokenization if save card input is not checked when autovaulting is set locally',
+      () => {
+        testContext.context.merchantConfiguration.vault.autoVault = true;
         testContext.context.saveCardInput.checked = false;
 
         return CardView.prototype.tokenize.call(testContext.context).then(() => {
@@ -2526,8 +2253,8 @@ describe('CardView', () => {
       }
     );
 
-    test('does not vault on tokenization if using guest checkout', () => {
-      testContext.context.model.isGuestCheckout = true;
+    test('does not vault on tokenization if autovaulting is set to false', () => {
+      testContext.context.model.vaultManagerConfig.autoVaultPaymentMethods = false;
 
       return CardView.prototype.tokenize.call(testContext.context).then(() => {
         expect(testContext.context.hostedFieldsInstance.tokenize).toBeCalledWith({ vault: false });
@@ -2564,6 +2291,7 @@ describe('CardView', () => {
       'does not clear fields after successful tokenization if merchant configuration includes clearFieldsAfterTokenization as false',
       () => {
         testContext.context.merchantConfiguration = {
+          vault: {},
           clearFieldsAfterTokenization: false
         };
         testContext.context.hostedFieldsInstance.tokenize.mockResolvedValue({ nonce: 'foo' });
@@ -2578,6 +2306,7 @@ describe('CardView', () => {
       'does not clear cardholder name field after successful tokenization if merchant configuration includes clearFieldsAfterTokenization as false',
       () => {
         testContext.context.merchantConfiguration = {
+          vault: {},
           clearFieldsAfterTokenization: false
         };
         testContext.context.hasCardholderName = true;
@@ -2657,7 +2386,7 @@ describe('CardView', () => {
     test(
       'does not update the active payment method when tokenize fails',
       () => {
-        testContext.context.hostedFieldsInstance.tokenize.mockRejectedValue(new Error('bad happen'));
+        fake.hostedFieldsInstance.tokenize.mockRejectedValue(new Error('bad happen'));
         jest.spyOn(testContext.model, 'addPaymentMethod').mockImplementation();
 
         return CardView.prototype.tokenize.call(testContext.context).then(throwIfResolves).catch(() => {
diff --git a/test/unit/views/payment-sheet-views/google-pay-view.js b/test/unit/views/payment-sheet-views/google-pay-view.js
index 80caca9..cae5a02 100644
--- a/test/unit/views/payment-sheet-views/google-pay-view.js
+++ b/test/unit/views/payment-sheet-views/google-pay-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../../src/lib/analytics');
 
 /* eslint-disable no-new */
 
@@ -23,7 +24,6 @@ describe('GooglePayView', () => {
     const googlePayButton = document.createElement('button');
 
     testContext.model = fake.model();
-    testContext.fakeClient = fake.client();
 
     testContext.div = document.createElement('div');
 
@@ -39,7 +39,7 @@ describe('GooglePayView', () => {
       }
     };
     testContext.googlePayViewOptions = {
-      client: testContext.fakeClient,
+      environment: 'sandbox',
       element: document.body.querySelector('.braintree-sheet.braintree-googlePay'),
       model: testContext.model,
       strings: {}
@@ -74,7 +74,6 @@ describe('GooglePayView', () => {
       email: 'foo@example.com'
     };
     jest.spyOn(btGooglePay, 'create').mockResolvedValue(testContext.fakeGooglePayInstance);
-    jest.spyOn(analytics, 'sendEvent').mockImplementation();
 
     testContext.FakePaymentClient = function FakePayment() {};
     testContext.FakePaymentClient.prototype.isReadyToPay = jest.fn().mockResolvedValue({ result: true });
@@ -126,7 +125,6 @@ describe('GooglePayView', () => {
     test('creates a GooglePayment component', () => {
       return testContext.view.initialize().then(() => {
         expect(btGooglePay.create).toBeCalledWith(expect.objectContaining({
-          client: testContext.view.client
         }));
         expect(testContext.view.googlePayInstance).toBe(testContext.fakeGooglePayInstance);
       });
@@ -139,7 +137,6 @@ describe('GooglePayView', () => {
 
         return testContext.view.initialize().then(() => {
           expect(btGooglePay.create).toBeCalledWith(expect.objectContaining({
-            client: testContext.view.client,
             googlePayVersion: 2
           }));
           expect(testContext.view.googlePayInstance).toBe(testContext.fakeGooglePayInstance);
@@ -154,7 +151,6 @@ describe('GooglePayView', () => {
 
         return testContext.view.initialize().then(() => {
           expect(btGooglePay.create).toBeCalledWith(expect.objectContaining({
-            client: testContext.view.client,
             googleMerchantId: 'foobar'
           }));
           expect(testContext.view.googlePayInstance).toBe(testContext.fakeGooglePayInstance);
@@ -169,12 +165,9 @@ describe('GooglePayView', () => {
     });
 
     test(
-      'configures payemnts client with PRODUCTION environment in production',
+      'configures payments client with PRODUCTION environment in production',
       () => {
-        const configuration = fake.configuration();
-
-        configuration.gatewayConfiguration.environment = 'production';
-        testContext.fakeClient.getConfiguration.mockReturnValue(configuration);
+        testContext.view.model.environment = 'production';
         jest.spyOn(global.google.payments.api, 'PaymentsClient');
 
         return testContext.view.initialize().then(() => {
@@ -186,12 +179,9 @@ describe('GooglePayView', () => {
     );
 
     test(
-      'configures payemnts client with TEST environment in non-production',
+      'configures payments client with TEST environment in non-production',
       () => {
-        const configuration = fake.configuration();
-
-        configuration.gatewayConfiguration.environment = 'sandbox';
-        testContext.fakeClient.getConfiguration.mockReturnValue(configuration);
+        testContext.view.model.environment = 'sandbox';
         jest.spyOn(global.google.payments.api, 'PaymentsClient');
 
         return testContext.view.initialize().then(() => {
@@ -377,7 +367,7 @@ describe('GooglePayView', () => {
 
       return testContext.view.tokenize().then(() => {
         expect(analytics.sendEvent).toBeCalledTimes(1);
-        expect(analytics.sendEvent).toBeCalledWith(testContext.fakeClient, 'googlepay.loadPaymentData.failed');
+        expect(analytics.sendEvent).toBeCalledWith('googlepay.loadPaymentData.failed');
       });
     });
 
@@ -400,7 +390,7 @@ describe('GooglePayView', () => {
 
       return testContext.view.tokenize().then(() => {
         expect(analytics.sendEvent).toBeCalledTimes(1);
-        expect(analytics.sendEvent).toBeCalledWith(testContext.fakeClient, 'googlepay.loadPaymentData.canceled');
+        expect(analytics.sendEvent).toBeCalledWith('googlepay.loadPaymentData.canceled');
       });
     });
 
@@ -482,26 +472,11 @@ describe('GooglePayView', () => {
     beforeEach(() => {
       jest.spyOn(assets, 'loadScript').mockResolvedValue();
       testContext.fakeOptions = {
-        client: testContext.fakeClient,
+        environment: 'sandbox',
         merchantConfiguration: testContext.model.merchantConfiguration
       };
     });
 
-    test(
-      'resolves with false when gatewayConfiguration does not have android pay',
-      () => {
-        const configuration = fake.configuration();
-
-        delete configuration.gatewayConfiguration.androidPay;
-
-        testContext.fakeOptions.client.getConfiguration.mockReturnValue(configuration);
-
-        return GooglePayView.isEnabled(testContext.fakeOptions).then(result => {
-          expect(result).toBe(false);
-        });
-      }
-    );
-
     test(
       'resolves with false when merhcantConfiguration does not specify Google Pay',
       () => {
@@ -597,5 +572,35 @@ describe('GooglePayView', () => {
         expect(result).toBe(true);
       });
     });
+
+    test('creates a payment client in test mode when using sandbox environment', () => {
+      jest.spyOn(global.google.payments.api, 'PaymentsClient').mockReturnValue({
+        isReadyToPay: jest.fn().mockResolvedValue({
+          result: true
+        })
+      });
+      testContext.fakeOptions.environment = 'sandbox';
+
+      return GooglePayView.isEnabled(testContext.fakeOptions).then(() => {
+        expect(global.google.payments.api.PaymentsClient).toBeCalledWith({
+          environment: 'TEST'
+        });
+      });
+    });
+
+    test('creates a payment client in production mode when using production environment', () => {
+      jest.spyOn(global.google.payments.api, 'PaymentsClient').mockReturnValue({
+        isReadyToPay: jest.fn().mockResolvedValue({
+          result: true
+        })
+      });
+      testContext.fakeOptions.environment = 'production';
+
+      return GooglePayView.isEnabled(testContext.fakeOptions).then(() => {
+        expect(global.google.payments.api.PaymentsClient).toBeCalledWith({
+          environment: 'PRODUCTION'
+        });
+      });
+    });
   });
 });
diff --git a/test/unit/views/payment-sheet-views/paypal-credit-view.js b/test/unit/views/payment-sheet-views/paypal-credit-view.js
index 18ca4e9..f1836cf 100644
--- a/test/unit/views/payment-sheet-views/paypal-credit-view.js
+++ b/test/unit/views/payment-sheet-views/paypal-credit-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../../src/lib/analytics');
 
 /* eslint-disable no-new */
 
diff --git a/test/unit/views/payment-sheet-views/paypal-view.js b/test/unit/views/payment-sheet-views/paypal-view.js
index b5f0324..450293f 100644
--- a/test/unit/views/payment-sheet-views/paypal-view.js
+++ b/test/unit/views/payment-sheet-views/paypal-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../../src/lib/analytics');
 
 /* eslint-disable no-new */
 
diff --git a/test/unit/views/payment-sheet-views/venmo-view.js b/test/unit/views/payment-sheet-views/venmo-view.js
index 16103d7..0552778 100644
--- a/test/unit/views/payment-sheet-views/venmo-view.js
+++ b/test/unit/views/payment-sheet-views/venmo-view.js
@@ -1,3 +1,4 @@
+jest.mock('../../../../src/lib/analytics');
 
 /* eslint-disable no-new */
 
@@ -17,8 +18,6 @@ describe('VenmoView', () => {
   });
 
   beforeEach(() => {
-    testContext.fakeClient = fake.client();
-
     testContext.model = fake.model();
     jest.spyOn(testContext.model, 'reportAppSwitchPayload').mockImplementation();
     jest.spyOn(testContext.model, 'reportAppSwitchError').mockImplementation();
@@ -30,7 +29,6 @@ describe('VenmoView', () => {
 
     testContext.model.merchantConfiguration.venmo = true;
     testContext.venmoViewOptions = {
-      client: testContext.fakeClient,
       element: document.body.querySelector('.braintree-sheet.braintree-venmo'),
       model: testContext.model,
       strings: {}
@@ -80,7 +78,7 @@ describe('VenmoView', () => {
     test('creates an Venmo component', () => {
       return testContext.view.initialize().then(() => {
         expect(btVenmo.create).toBeCalledWith(expect.objectContaining({
-          client: testContext.view.client
+          authorization: testContext.view.model.authorization
         }));
         expect(testContext.view.venmoInstance).toBe(testContext.fakeVenmoInstance);
       });
@@ -93,7 +91,7 @@ describe('VenmoView', () => {
 
         return testContext.view.initialize().then(() => {
           expect(btVenmo.create).toBeCalledWith(expect.objectContaining({
-            client: testContext.view.client,
+            authorization: testContext.view.model.authorization,
             allowNewBrowserTab: false
           }));
         });
@@ -257,27 +255,11 @@ describe('VenmoView', () => {
   describe('isEnabled', () => {
     beforeEach(() => {
       testContext.options = {
-        client: testContext.fakeClient,
         merchantConfiguration: testContext.model.merchantConfiguration
       };
       jest.spyOn(btVenmo, 'isBrowserSupported').mockReturnValue(true);
     });
 
-    test(
-      'resolves with false when Venmo Pay is not enabled on the gateway',
-      () => {
-        const configuration = fake.configuration();
-
-        delete configuration.gatewayConfiguration.payWithVenmo;
-
-        testContext.fakeClient.getConfiguration.mockReturnValue(configuration);
-
-        return VenmoView.isEnabled(testContext.options).then(result => {
-          expect(result).toBe(false);
-        });
-      }
-    );
-
     test(
       'resolves with false when Venmo Pay is not enabled by merchant',
       () => {