Multiple setup calls in Braintree result in receiving multiple onPaymentMethodReceived events

In my Angular application, I am working with an angularUI modal window where I want to integrate the Drop In form provided by Braintree for processing payments. To achieve this, I have created a standard form (partial.html):

<form id="creditCard" >
   <div id="dropin"></div>  
   <button type="submit" id="btnPay" >Pay</button>  
</form>

To display the modal, I use the following code:

var modalInstance = $modal.open({
   templateUrl: 'partial.html',
   controller: 'ModalController'
});

The ModalController is responsible for initializing the Braintree setup as shown below:

braintree.setup($scope.clientToken, 'dropin', {
   container: 'dropin',
   onPaymentMethodReceived: function (result) {
       $scope.$apply(function() {
           $scope.success = true;
           // Additional logic can be added here
       });
   }
});

Everything works smoothly in displaying the Braintree Drop In form and capturing credit card details including expiration date. However, a challenge arises when the modal is called multiple times, causing the onPaymentMethodReceived() event to trigger multiple times due to each setup execution.

I am seeking suggestions or solutions on how to prevent this issue. Is there a way to remove the event handler associated with onPaymentMethodReceived()? It's essential to call the setup function multiple times because the clientToken may change each time the modal is opened.

Your assistance or guidance on resolving this matter would be greatly appreciated. Thank you.

Answer №1

When working with angular, it seems like calling braintree.setup multiple times is inevitable. This could be due to various reasons, such as the setup being called in a controller that is instantiated multiple times during a browsing session, like in a cart or checkout controller.

One approach to address this issue is by implementing the following solution:

$rootScope.success = false;
braintree.setup($scope.clientToken, 'dropin', {
   container: 'dropin',
   onPaymentMethodReceived: function (result) {
       if(!$rootScope.success) {
           $scope.$apply(function() {
               $rootScope.success = true;
               // Additional actions can be performed with the result here
           });
       }
   }
});

I discovered that even though I couldn't prevent the callback from firing multiple times (the number of times seemed to increase each time I revisited the view), I was able to determine whether my actions had already been executed in response to the callback. Instead of relying on resetting $scope.success when leaving the view, I chose to use $rootScope to ensure only one execution overall, regardless of how many times the controller was reinstantiated. By setting $rootScope.success = false within the controller, the callback will only succeed once each time the controller is loaded.

Answer №2

As far as I know, the API takes care of this when you use the teardown function:

In some cases, it may be necessary to remove your integration with braintree.js. This is common in situations like single page applications, modal flows, and other scenarios where managing state is important. [...] Calling the teardown function will clean up any DOM elements, event handlers, popups, or iframes that were created by the integration.

(I have not tested this yet)

Answer №3

Arpad Tamas' link no longer contains the information, so I am sharing BrainTree's info here for future reference. It took me a bit of searching to find it through Google.

In certain situations, you may need to remove your Braintree.js integration. This is common in single page applications, modal flows, and other scenarios where state management is crucial. When you call braintree.setup, you can add a callback to onReady which will give you an object with a teardown method.

Executing teardown will clean up any DOM nodes, event handlers, popups, and iframes created by the integration. Furthermore, teardown includes a callback that signals when it's safe to proceed.

var checkout;

braintree.setup('CLIENT_TOKEN_FROM_SERVER', 'dropin', {
  onReady: function (integration) {
    checkout = integration;
  }
});

// When you want to dismantle your integration
checkout.teardown(function () {
  checkout = null;
  // braintree.setup can be called again safely!
});

You can only use teardown once per .setup call. If you try calling this method while another teardown is underway, you'll get an error saying Cannot call teardown while in progress. After completion, subsequent calls to teardown will result in an error message: Cannot teardown integration more than once.

I've encapsulated this code in a function that I run every time the corresponding checkout ionic view is accessed.

$scope.$on('$ionicView.enter', function() {
    ctrl.setBraintree(CLIENT_TOKEN_FROM_SERVER);
});

var checkout;

ctrl.setBrainTree = function (token) {
    braintree.setup(token, "dropin", {
        container: "dropin-container",

        onReady: function (integration) {
            checkout = integration;
            $scope.$emit('BTReady');
        },

        onPaymentMethodReceived: function(result) {
            ...
        },

        onError: function(type) {
            ...
        }
    });

    // Avoids calling checkout when entering the view for the first time (not initialized yet).
    if (checkout) {
    // When you are ready to dismantle your integration
        checkout.teardown(function () {
            checkout = null; // braintree.setup can be called again safely!
        });
    }
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Looking for assistance in setting up a personalized slideshow to automatically play on its

Recently, I have taken responsibility for a project that was initiated by someone else. The website contains a customized slideshow on its homepage. To meet the client's requirements, I have already made some alterations to the appearance and feel of ...

What are the security benefits of using Res.cookie compared to document.cookie?

When it comes to setting cookies to save data of signed in members, I faced a dilemma between two options. On one hand, there's res.cookie which utilizes the Express framework to set/read cookies on the server-side. On the other hand, there's d ...

Using ajax to retrieve quotes and authors from online sources

I've recently started learning javascript and I'm currently working on the Random Quote Machine project on freecodecamp. The main goal is to show a random quote along with its author when the user clicks the "New Quote" button. However, I'm ...

Complete a form on an external website using either C# or Javascript

I am looking for a solution to automatically fill and submit a form from a specific URL, as I need to trigger this process from my domoticz. Despite attempting different methods like using AJAX or injecting JavaScript, I keep encountering issues with the S ...

Developing a performant RESTful API using Node.js and MongoDB

As I embark on setting up my first RESTful API with Express and Mongo DB, I am focused on creating a clear pattern that will make the project easy to pick up after a break or for new team members joining. The idea is that whenever an endpoint requires mod ...

Saving base64 encoded pdf on Safari

I am facing an issue with a POST call that returns a base64 PDF. The problem arises when trying to convert it to a Blob and download it using Safari browser. This method works perfectly in all other browsers. openPdf = () => { const sendObj = { ...

"Error: imports are undefined" in the template for HTML5 boilerplate

After setting up an HTML5 Boilerplate project in WebStorm, I navigate to the localhost:8080/myproject/src URL to run it. Within the src folder, there is a js directory structured like this: libraries models place.model.ts place.model.js addr ...

Submit the form without displaying any output in the browser, not even in the view source code

I have a basic form that needs to be submitted multiple times, but I want the submission process to be hidden from the browser. Simply using "hidden" or "display:none" won't completely hide the form code when viewing the page source. I tried using PHP ...

Issue with submitting form using q-select

After incorporating a q-select element into my form, I noticed that the submit event is not triggering. Strangely, when I remove the q-select element, the submit event works as expected. <q-form @submit.prevent="addNewRole" class="q ...

Executing a curl POST request within an npm script

I recently added a new script to my npm scripts in the package.json file, but I'm running into issues due to the single and double quotes. The problem seems to be with the internal double quotes within the payload. "slack": "curl -X POST --data-urlen ...

Is it possible to trigger a click event exclusively on a parent element?

One feature I'm trying to implement in my app is the ability to click on an item, such as changing the background color or text, and have a modal pop up with a color picker to customize the color of that specific item. However, I've run into an i ...

Is there a way for me to make this Select update when onChange occurs?

I am facing an issue with a react-select input that is supposed to display country options from a JSON file and submit the selected value. Currently, when a selection is made, the field does not populate with the selection visually, but it still sends the ...

Continuously decrease a sequence of identical numbers in an array through recursion

One of the key challenges was to condense an array of numbers (with consecutive duplicates) by combining neighboring duplicates: const sumClones = (numbers) => { if (Array.isArray(numbers)) { return numbers.reduce((acc, elem, i, arr) => { if ( ...

How to Capture Back Button Press in Angular 2

I'm currently working on detecting whether the back button was pressed upon loading this component. My goal is to determine within the ngOnInit() method if the Back button was clicked in order to avoid clearing all of my filters. Check out the code be ...

Ways to navigate to a different page while displaying an alert message?

Desperately seeking assistance with redirecting to another page and then triggering an alert("HELLO") once the new page is loaded. I have attempted the following approach: $.load(path, function() { alert.log("HELLO"); }); But using window.location o ...

Issues with ReactJS routing functionality

Apologies if this question has been asked before. I am facing an issue that I can't seem to resolve or find a solution for. My React Router setup includes an IndexRoute that works fine (localhost:8080/ or localhost:8080 renders). The templates are nes ...

Webster Barron code script

I'm attempting to replicate the concept of this video https://www.superhi.com/video/barron-webster using text, but I am encountering difficulties in achieving the desired effect. The design text is currently overlapping my name and displaying in rev ...

Rails: Issues with loading nested remote form on page load

Within my Rails application, I have a form structured as follows: [ Parent list item 1 ] [ Parent list item 2 ] [ Parent list item 3 - expanded ] [ Child list item 1 ] Child inline input Child submit button ------------------ [Parent in ...

Unusual bug in version resolution encountered during bower installation due to angular.js angles

Attempting to update my angular version has resulted in confusion with bower. My dependencies are as follows: "dependencies": { "underscore":"*", "momentjs":"~2.2.1", "bootstrap": "~3.0.0", "jquery": "~1.9.1", "jquery.cookie": "*" ...

Attempting to get a webGL game downloaded from Unity to automatically enter fullscreen mode

Can someone help me with my Unity webGL game issue? I downloaded it from the internet, but I'm not sure what version of Unity was used to create it. When the game starts playing, it displays in a small canvas along with other elements like the Unity ...