Repository: braintree/braintree-web-drop-in
Pull request #730 (2021-04-29 20:13:22)

feat: add option to opt out of vaulting PayPal accounts
### Summary

Add option to opt out of vaulting PayPal accounts.

### Checklist

- [x] Added a changelog entry

### Authors
> List GitHub usernames for everyone who contributed to this pull request.

- @crookedneighbor 
- @jplukarski 



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 589755a..a472a59 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ unreleased
 - Fix issue where scrollbar appears for saved payment methods (#724)
 - Fix bug in initialization process where loading spinner would never resolve (#716)
 - Add `changeActiveView` event to track when the Drop-in updates the active view presented to the customer
+- Add option to not vault PayPal accounts with `paypal.vault.vaultPayPal = false` or `paypalCredit.vault.vaultPayPal = false`
 
 1.27.0
 ------
diff --git a/src/index.js b/src/index.js
index dda8e14..884c434 100644
--- a/src/index.js
+++ b/src/index.js
@@ -146,6 +146,8 @@ var VERSION = '__VERSION__';
  * @param {string} [currency] The currency code of the amount, such as `USD`. Required when using the Checkout flow.
  * @param {object} [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] Configuration for vaulting PayPal accounts. 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.vaultPayPal=true] Whether or not to vault the PayPal account upon tokenization.
  */
 
 /** @typedef {object} applePayCreateOptions The configuration options for Apple Pay.
diff --git a/src/views/payment-sheet-views/base-paypal-view.js b/src/views/payment-sheet-views/base-paypal-view.js
index a26ce38..b8f45ef 100644
--- a/src/views/payment-sheet-views/base-paypal-view.js
+++ b/src/views/payment-sheet-views/base-paypal-view.js
@@ -58,8 +58,18 @@ BasePayPalView.prototype.initialize = function () {
         return paypalInstance.createPayment(self.paypalConfiguration).catch(reportError);
       },
       onAuthorize: function (data) {
+        // NEXT_MAJOR_VERSION change out this vaultPayPal property
+        // to something more generic, such as vaultOnTokenization so
+        // all the vault objects can have the same shape (instead
+        // of being specific to PayPal accounts here)
+        var shouldNotVault = self.paypalConfiguration.vault && self.paypalConfiguration.vault.vaultPayPal === false;
+
+        if (shouldNotVault) {
+          data.vault = false;
+        }
+
         return paypalInstance.tokenizePayment(data).then(function (tokenizePayload) {
-          if (self.paypalConfiguration.flow === 'vault' && !self.model.isGuestCheckout) {
+          if (!shouldNotVault && self.paypalConfiguration.flow === 'vault' && !self.model.isGuestCheckout) {
             tokenizePayload.vaulted = true;
           }
           self.model.addPaymentMethod(tokenizePayload);
diff --git a/src/views/payment-sheet-views/card-view.js b/src/views/payment-sheet-views/card-view.js
index 3870ac6..f936905 100644
--- a/src/views/payment-sheet-views/card-view.js
+++ b/src/views/payment-sheet-views/card-view.js
@@ -69,6 +69,10 @@ CardView.prototype.initialize = function () {
     classList.remove(this.getElementById('save-card-field-group'), 'braintree-hidden');
   }
 
+  // NEXT_MAJOR_VERSION change out this vaultCard property
+  // to something more generic, such as vaultOnTokenization so
+  // all the vault objects can have the same shape (instead
+  // of being specific to cards here)
   if (this.merchantConfiguration.vault.vaultCard === false) {
     this.saveCardInput.checked = false;
   }


Test patch:
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 27e7f93..0ae8a8c 100644
--- a/test/unit/views/payment-sheet-views/base-paypal-view.js
+++ b/test/unit/views/payment-sheet-views/base-paypal-view.js
@@ -479,8 +479,7 @@ describe('BasePayPalView', () => {
         const paypalInstance = testContext.paypalInstance;
         const model = testContext.model;
         const fakePayload = {
-          foo: 'bar',
-          vaulted: true
+          foo: 'bar'
         };
 
         model.isGuestCheckout = false;
@@ -504,7 +503,10 @@ describe('BasePayPalView', () => {
 
           setTimeout(() => {
             expect(model.addPaymentMethod).toBeCalledTimes(1);
-            expect(model.addPaymentMethod).toBeCalledWith(fakePayload);
+            expect(model.addPaymentMethod).toBeCalledWith({
+              foo: 'bar',
+              vaulted: true
+            });
 
             done();
           }, 1);
@@ -588,6 +590,56 @@ describe('BasePayPalView', () => {
       }
     );
 
+    test(
+      'does not add `vaulted: true` to the tokenization payload if flow is vault and is not guest checkout, but the merchant opts out of vaulting on the client',
+      done => {
+        const paypalInstance = testContext.paypalInstance;
+        const model = testContext.model;
+
+        model.merchantConfiguration.paypal.vault = {
+          vaultPayPal: false
+        };
+        model.isGuestCheckout = false;
+
+        paypalInstance.tokenizePayment.mockResolvedValue({
+          foo: 'bar'
+        });
+        paypalInstance.paypalConfiguration = {
+          flow: 'vault',
+          vault: {
+            vaultPayPal: false
+          }
+        };
+        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 = {
+            foo: 'bar'
+          };
+
+          onAuthFunction(tokenizeOptions);
+
+          expect(paypalInstance.tokenizePayment).toBeCalledTimes(1);
+          expect(paypalInstance.tokenizePayment).toBeCalledWith({
+            foo: 'bar',
+            vault: false
+          });
+
+          setTimeout(() => {
+            expect(model.addPaymentMethod).toBeCalledTimes(1);
+            expect(model.addPaymentMethod).toBeCalledWith({
+              foo: 'bar'
+            });
+
+            done();
+          }, 1);
+        });
+      }
+    );
+
     test('reports errors from tokenizePayment', done => {
       const paypalInstance = testContext.paypalInstance;
       const model = testContext.model;