Repository: braintree/braintree-web-drop-in (js)

Issue:
Multiple Drop-in instances may cause PayPal to fail to load
### General information

* SDK/Library version: `1.9.4` and `1.22.0`.
* Environment: Sandbox and Production.
* Browser and OS: Windows 7 64-bit, Chrome, Version 79.0.3945.130 (Official Build) (64-bit)

### Issue description

To reproduce the issue:
1. Visit https://www.bitarmory.com/bogus
1. In the DropIn UI, select "Card" for "Choose a way to pay".
   ![image](https://user-images.githubusercontent.com/478118/73320114-2f9e8e00-41f3-11ea-9432-5e2c903e75cb.png)
1. Open DevTools, and type in console: `dropin.clearSelectedPaymentMethod()`. The `dropin` variable is a saved instance of the `dropinInstance` after initialization.
1. After calling `dropin.clearSelectedPaymentMethod()` method, the UI does not go back to payment options.
   ![image](https://user-images.githubusercontent.com/478118/73320210-6b395800-41f3-11ea-94e1-8dc52abbc597.png)
1. Calling `dropin._mainView.toggleAdditionalOptions()` works; but this API is undocumented. :disappointed: 
   ![image](https://user-images.githubusercontent.com/478118/73320313-b81d2e80-41f3-11ea-9b0f-c6aeaa7a3a62.png)

The issue is in production and sandbox for versions `1.22.0` latest and `1.9.0`.




Pull request description:
fix: issue where PayPal button could render in the wrong drop-in
closes #590

### Summary

We weren't being specific enough for the PayPal SDK about where to render the paypal buttons. This scopes them to the containing div.

### Checklist

- [x] Added a changelog entry

### Authors
> List GitHub usernames for everyone who contributed to this pull request.

- @crookedneighbor 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56912e6..ac21222 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,8 @@ unreleased
   ```
 - Apple Pay
   - Fix issue where Apple Pay view was using `canMakePaymentsWithActiveCard` incorrectly
+- PayPal
+  - Fix issue where PayPal buttons would not render correctly when multiple Drop-in instances were loaded (closes #590)
 
 1.25.0
 ------
diff --git a/src/views/payment-sheet-views/base-paypal-view.js b/src/views/payment-sheet-views/base-paypal-view.js
index ca2b35c..6ddb6ce 100644
--- a/src/views/payment-sheet-views/base-paypal-view.js
+++ b/src/views/payment-sheet-views/base-paypal-view.js
@@ -30,6 +30,7 @@ BasePayPalView.prototype.initialize = function () {
   var self = this;
   var paypalType = isCredit ? 'paypalCredit' : 'paypal';
   var paypalConfiguration = this.model.merchantConfiguration[paypalType];
+  var dropinWrapperId = '#braintree--dropin__' + this.model.componentID;
 
   this.paypalConfiguration = assign({}, paypalConfiguration);
 
@@ -90,6 +91,8 @@ BasePayPalView.prototype.initialize = function () {
       checkoutJSConfiguration.funding.disallowed.push(global.paypal.FUNDING.CREDIT);
     }
 
+    buttonSelector = dropinWrapperId + ' ' + buttonSelector;
+
     return global.paypal.Button.render(checkoutJSConfiguration, buttonSelector).then(function () {
       self.model.asyncDependencyReady();
       setupComplete = true;


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 477a628..1ff0358 100644
--- a/test/unit/views/payment-sheet-views/base-paypal-view.js
+++ b/test/unit/views/payment-sheet-views/base-paypal-view.js
@@ -150,7 +150,10 @@ describe('BasePayPalView', () => {
     test('calls paypal.Button.render', () => {
       return testContext.view.initialize().then(() => {
         expect(testContext.paypal.Button.render).toBeCalledTimes(1);
-        expect(testContext.paypal.Button.render).toBeCalledWith(expect.any(Object), '[data-braintree-id="paypal-button"]');
+        expect(testContext.paypal.Button.render).toBeCalledWith(
+          expect.any(Object),
+          expect.stringMatching(/#braintree--dropin__.*\[data-braintree-id="paypal-button"\]/)
+        );
       });
     });
 
@@ -697,7 +700,10 @@ describe('BasePayPalView', () => {
 
       test('uses the PayPal button selector', () => {
         return testContext.view.initialize().then(() => {
-          expect(testContext.paypal.Button.render).toBeCalledWith(expect.any(Object), '[data-braintree-id="paypal-button"]');
+          expect(testContext.paypal.Button.render).toBeCalledWith(
+            expect.any(Object),
+            expect.stringMatching(/#braintree--dropin__.*\[data-braintree-id="paypal-button"\]/)
+          );
         });
       });
     });
@@ -766,7 +772,10 @@ describe('BasePayPalView', () => {
         testContext.view._isPayPalCredit = true;
 
         return testContext.view.initialize().then(() => {
-          expect(testContext.paypal.Button.render).toBeCalledWith(expect.any(Object), '[data-braintree-id="paypal-credit-button"]');
+          expect(testContext.paypal.Button.render).toBeCalledWith(
+            expect.any(Object),
+            expect.stringMatching(/#braintree--dropin__.*\[data-braintree-id="paypal-credit-button"\]/)
+          );
         });
       });