braintree / braintree-web-drop-in

Braintree Drop-in for the web
MIT License
200 stars 126 forks source link

Multiple Drop-in instances may cause PayPal to fail to load #590

Closed bchavez closed 3 years ago

bchavez commented 4 years ago

General information

Issue description

To reproduce the issue:

  1. Visit https://www.bitarmory.com/bogus
  2. In the DropIn UI, select "Card" for "Choose a way to pay". image
  3. Open DevTools, and type in console: dropin.clearSelectedPaymentMethod(). The dropin variable is a saved instance of the dropinInstance after initialization.
  4. After calling dropin.clearSelectedPaymentMethod() method, the UI does not go back to payment options. image
  5. Calling dropin._mainView.toggleAdditionalOptions() works; but this API is undocumented. :disappointed: image

The issue is in production and sandbox for versions 1.22.0 latest and 1.9.0.

crookedneighbor commented 4 years ago

clearSelectedPaymentMethod is only intended to be used once a payment method has been tokenized. Not for bringing the customer programmatically back to the payment option selection view.

Can you explain your use case where you’d change the view back programmatically instead of leaving it up to the customer to return? (I’m sure there’s a good reason, just curious about it)

bchavez commented 4 years ago

Hi Blade,

Thanks for such a quick response. I was trying to get the following UX to work:

image

The user has a 3 left-vertical tab that they can click (:one:, :two: or :three:). I was hoping at a very minimum to "go back to options" if "Credit Card" :one: or "Paypal" :two: verticle tabs were clicked. Or even better, keep the left-verticle tabs and drop-in UI in sync.

The tab page (div) with the drop-in UI would get swapped out when "Bitcoin & others" :three: is clicked.

I was probably asking for too much; what would you recommend? Would you say it's safe to use these undocumented APIs to change UI pages for drop-in as long as I stick to a specific version? Or, just don't do it? You can close the issue if you think this issue is not in scope for drop-in UI.

Thanks for the help! Brian

bchavez commented 4 years ago

Oh, maybe I can have two drop-in instances; one each per tab page and limit the payment options for each: one tab page drop in instance for credit and another tab page drop in instance for paypal.

crookedneighbor commented 4 years ago

To be honest, it sounds like you’d be better off using the underlying sdk instead if you want that level of customization.

2 dropin instances would also accomplish what you are going for.

I would not recommend using those private methods. They are only intended to be used internally, and may change in behavior (or disappear completely) in future. Always review them when upgrading the sdk.

crookedneighbor commented 4 years ago

Here's the Github Repo for the raw sdk: https://github.com/braintree/braintree-web And the client sdk documentation: https://braintree.github.io/braintree-web/current/

bchavez commented 4 years ago

Hi Blade,

Thanks a bunch! I really appreciate your help. Actually I've been struggling with getting this UX to work with two drop-in instances. I might have to bail out of this "two drop-in panel" idea and go the route you described earlier.

:x: For example, if I load Credit drop-in first, and the PayPal drop-in second, the Paypal button never shows up. I can't get both drop-in instances to work on the same page in this loading sequence.

image

:heavy_check_mark: If I load the Paypal drop-in first, then the Credit drop-in second, reversing the load order, both drop-ins work on the same page. :man_shrugging:

image

I even tried using two different ClientTokens for each panel. Still no dice. Kinda out of ideas atm. :game_die: Here's the config setup for each:

function LoadCredit() {
   braintree.dropin.create({
         authorization: '@this.Model.ClientToken',
         container: '#dropin-credit',
         paymentOptionPriority: ['card']
      },
      function(err, dropin) {
         creditInstance = dropin;
         InitDropin(err, dropin);
      });
}
function LoadPaypal() {
   braintree.dropin.create({
         authorization: '@this.Model.ClientToken',
         container: '#dropin-paypal',
         paypal: {
               flow: 'checkout',
               amount: '10.00',
               currency: 'USD',
               enableShippingAddress: false,
               displayName: 'Product Purchase',
               buttonStyle: {
                     color: 'gold',
                     shape: 'pill',
                     size: 'large'
                  }
            },
         paymentOptionPriority: ['paypal']
      },
      function(err, dropin) {
         paypalInstance = dropin;
         InitDropin(err, dropin);
      });

I don't see any errors in the console when the PayPal drop-in doesn't load correctly. However, I do notice there are no iframe children in the case where the PayPal drop-in fails to load.

I'm not sure I'm going to rely on a DOM load ordering hack for the payment page because it seems brittle and indicative of something more problematic somewhere. I may just have to jump ship on this "two drop-in panel" idea and go the route you mentioned above.

Please let me know if you see anything obvious I'm missing. I would prefer to use drop-in when and where possible, but I guess it might not be the right direction. :crying_cat_face:

Thanks a bunch, Brian Chavez

crookedneighbor commented 4 years ago

That sounds like a bug. I'm going to re-open this for now.

bchavez commented 4 years ago

Hi Blade,

I have a minimal HTML file that will reproduce the issue. I hope it's useful for testing:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <title>Drop-in Load: Paypal, Credit</title>

   <script src="https://js.braintreegateway.com/web/dropin/1.22.0/js/dropin.js"></script>
</head>
<body>
   <h3>Copy/paste Authorization here:</h3>
   <textarea id="client-token" rows="5" cols="70"></textarea><br/>
   <button onclick="SaveValue()">Save Value To Cookie</button><br/>
   <span id="status"></span>

   <!--<h3>Commands</h3>

   <button onclick="LoadCredit()">Load Credit</button>
   <button onclick="LoadPaypal()">Load Paypal</button>-->

   <hr />

   <h4>Credit Drop-in</h4>
   <div id="dropin-credit" style="width: 400px;"></div>

   <hr />

   <h4>Paypal Drop-in</h4>
   <div id="dropin-paypal" style="width: 400px;"></div>

   <hr />

   <script type="text/javascript">
      function SaveValue() {
         var token = document.getElementById('client-token');
         document.cookie = "auth=" + token.value;
         var status = document.getElementById("status");
         status.innerText = "Refresh browser now, will use auth key on reload!";
      }

      var paypalInstance = null;
      var creditInstance = null;

      window.addEventListener('DOMContentLoaded',
         (event) => {
            var token = document.getElementById('client-token');
            if( !document.cookie ) return;

            token.value = document.cookie.substring(5);

            braintree.dropin.create({
                  authorization: token.value,
                  container: '#dropin-credit',
                  paymentOptionPriority: ['card']
               },
               function(err, dropin) {
                  console.log(err);
                  creditInstance = dropin;
               });

            braintree.dropin.create({
                  authorization: token.value,
                  container: '#dropin-paypal',
                  paypal: {
                        flow: 'checkout',
                        amount: '10.00',
                        currency: 'USD',
                        enableShippingAddress: false,
                        displayName: 'Product Purchase',
                        buttonStyle: {
                              color: 'gold',
                              shape: 'pill',
                              size: 'large'
                           }
                     },
                  paymentOptionPriority: ['paypal']
               },
               function(err, dropin) {
                  console.log(err);
                  paypalInstance = dropin;
               });
         });

      function LoadCredit() {
      }

      function LoadPaypal() {
      }
   </script>
</body>
</html>

Set the text box to a client token, click the save button, and then finally refresh the browser page, and you should see the Paypal instance fail as shown below:

screen_3682

Thanks, Brian

cosmin-novac commented 3 years ago

Multiple drop-in instances are generally an issue for the paypal buttons. In my case, I have two dropin instances, and both paypal buttons are rendered inside the first parent:

image

crookedneighbor commented 3 years ago

Yeah, I think I know exactly what the issue is. https://github.com/braintree/braintree-web-drop-in/blob/d58211e523545aab6c314bf0a9d10e23b2bec4f4/src/views/payment-sheet-views/base-paypal-view.js#L93

The button selector variable here needs to be more specific to each instance, which should be doable.

crookedneighbor commented 3 years ago

Gonna re-open this until the release actually goes out.

crookedneighbor commented 3 years ago

This is now fixed in v1.26.0