'Braintree and Angular.JS drop in integration' post illustration

Braintree and Angular.JS drop in integration

avatar

During drop in integration of Braintree into Angular.JS application we have faced several surprising caveats. This was the primary reason for current post to be born. We would like to share our expreince and solutions that we've learnt during this process.

The very first thing one needs is to load 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 oclazyload library. Our controller to launch UI Bootstrap Modal and load Braintree client code will look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 Braintree client library and get new Braintree token from our server. The code for getting token from the server is stubbed out. Here we talk about client-side integration of Braintree, leaving server-side for your discretion.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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 action, because submit event will be processed by 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 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 payment form and validate it before payment. Here is the code for modal controller that does exactly this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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 Submit button, so that user won't be able to press it, until Braintree client library will be fully initialized. braintree.setup takes a while to send request to Braintree servers and get payment nonce in response.

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

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

Hope this was helpful!

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

If you're looking for a developer or considering starting a new project,
we are always ready to help!