Braintree and Angular.js Drop-in Integration

During the drop in integration of Braintree into an AngularJS application, we faced several surprising caveats. This was the primary reason for the current post to be born. We would like to share our experience and solutions that we’ve learned during this process.

The very first thing one needs is to load the client-side Braintree library into the web application. Surely it can be loaded directly from the HTML code, and sometimes it is appropriate. But in the more difficult case, when you want to use Braintree only in specific modals, you will have to load it from JavaScript. We will show how to do it in this case. We will use the oclazyload library. Our controller to launch UI Bootstrap Modal and load Braintree client code will look like this:

app.controller('HomeCtrl', ['$uibModal', '$ocLazyLoad', '$log',
  function ($uibModal, $ocLazyLoad, $log) {
    var vm = this;

    // Get token from server
    vm.generateToken = function() {
      $log.debug('Get newly generated Braintree token from your server');
      return 'abc123';
    }

    vm.openModal = function () {
      var modal = $uibModal.open({
        templateUrl: 'modal.html',
        controller: 'ModalCtrl as ctrl',
        resolve: {
          braintree: () => $ocLazyLoad.load(
              'https://js.braintreegateway.com/v2/braintree.js'
            ),
          token: () => vm.generateToken()
        },
      });
    };
  }]);

Here we load the Braintree client library and get a new Braintree token from our server. The code for getting a token from the server is stubbed out. Here, we discuss client-side integration with Braintree, leaving server-side integration to your discretion.

Our modal will use the drop-in method to integrate Braintree. It will have a div placeholder for Braintree and, which is important, some custom fields, in this example, the ‘quantity’ custom field. HTML code for modal:

<script type="text/ng-template" id="modal.html">
  <form name="paymentForm" novalidate role="form">
      <div class="modal-header">
          <h3 class="modal-title">Payment Modal Demo</h3>
      </div>
      <div class="modal-body">
          <input type="number" name="quantity" placeholder="Enter any quantity"
              ng-model="ctrl.quantity" required></input>
          <div id="payment-form"></div>
          <hr/>
          <span>{{ctrl.serverError}}</span>
      </div>
      <div class="modal-footer">
          <button class="btn btn-primary" type="submit"
              ng-disabled="ctrl.disablePay">Submit</button>
          <button class="btn btn-warning" type="button"
              ng-click="ctrl.cancel()">Cancel</button>
      </div>
  </form>
</script>

Important notice here is that our form doesn’t have an action, because the submit event will be processed by the Braintree client code. This is the requirement of drop-in integration. But! You will be able to drive a wedge into submit event processing! This is necessary if you want to collect additional payment information. See below for details.

The Braintree docs state that the Drop In integration method: “Can only collect information included on the payment form.” This is misleading. In practice, you actually can collect custom information in a payment form and validate it before payment. Here is the code for the modal controller that does exactly this:

app.controller('ModalCtrl', ['$uibModalInstance', '$log', '$scope', '$q', 'token',
  function ($uibModalInstance, $log, $scope, $q, token) {
      var vm = this;

      vm.disablePay = true;

      braintree.setup(token, 'dropin', {
        container: 'payment-form',
        onPaymentMethodReceived: (data) => vm.submit($scope.paymentForm, data.nonce),
        onReady: () => {
          $timeout(() => {
            vm.disablePay = false;
          }, 0);
        },
        onError: (type, message) => {
          $timeout(() => {
            vm.serverError = message;
          }, 0);
        }
      });

      // Send to server nonce and custom data to execute payment
      vm.executePayment = function(nonce, quantity) {
        $log.debug('Send request to your server to execute payment');
        return $q.when('');
      }

      vm.submit = function(form, nonce) {
        if (form.$valid) {
          vm.executePayment(nonce, parseInt(quantity, 10)).then((result) => {
            delete vm.serverError;
          }, (error) => {
            form.$setPristine();
            vm.serverError = error;
          });
        }
      }

      vm.cancel = function () {
        $uibModalInstance.dismiss('cancel');
      };
  }]);

First, we disable the Submit button so that the user won’t be able to press it until the Braintree client library is fully initialized. braintree.setup takes a while to send a request to Braintree servers and get a payment nonce in response.

When the user fills in payment information and clicks on the submit button, the Braintree client will handle the submit event. It will pass the payment nonce to onPaymentMethodReceived hook. We will set our function for this hook. Our function will validate custom fields, and if they are valid, it will send the payment nonce along with the custom fields to our server.

There is also onError hook, which will be fired if any payment error arises. We should intercept it as well.

Hope this was helpful!

You can view the full code for this article on a plunkr.