Looking for a payment solution for your Spring Boot app? If so, then you should check out Braintree Direct.

Why? For starters, Braintree is a division of PayPal. So if you’re looking for PayPal integration, then you’re all set.

Beyond that, though, you can use Braintree to accept Most major credit cards, Apple Pay, Google Pay, and Venmo.

What’s not to love?

In this article, we’ll go over how you can implement a Braintree Direct solution within your Spring Boot application.

Feel free to review the entire source for this project on GitHub.

 

The Use Case

As usual, we’ll start with the use case because we’re business-focused around these parts.

Your boss, Smithers, just showed up in your office and told you that the company needed a payment solution for a new e-commerce website.

Smithers is clearly frustrated because he’s never rolled out a payment solution before. “I’ve always done admin screens!” he says in exasperation.

No problem, you reply. You assure him that you can integrate a Braintree solution into the app.

Smithers leave your office smiling.

 

Assumptions

To get the most out of this demo, you’ll need a working knowledge of Java, Spring Boot, and Thymeleaf. There are plenty of tutorials online that will help you understand that tech stack if you’re unfamiliar with any of it.

You should also have a basic understanding of Braintree. That doesn’t mean you need to be an expert in the integration technology (that’s why you’re reading this article, after all). But you should know how to set up an account and configure it to accept various payments.

For the purposes of this demo, the application is only accepting credit card payments. It’s not accepting PayPal, Apple Pay, Google Pay, or Venmo.

 

Playing in the Sandbox

Fortunately, Braintree offers a sandbox environment where you can play to your heart’s content with fake credit card transactions. That way, you can make sure that your code works without having to max out your card.

You’ll need to set up a sandbox in Braintree if you want to use the demo code locally.

Once you have that set up, you’ll get a Merchant ID. You can find it by viewing your Merchant Account info located under the Account menu at the top of the screen.

merchantaccount

 

You’ll also need a private key and public key. You can access those by selecting API Keys under the Settings menu.

apikeys

 

The POM File

You need to update your POM file to include the Braintree Java client library. Add the following dependency:

1
2
3
4
5
<dependency>
    <groupId>com.braintreepayments.gateway</groupId>
    <artifactId>braintree-java</artifactId>
    <version>2.79.0</version>
</dependency>

That’s the only addition you need to make for this project.

 

The Config Class

Next, you need to create a config class. Here’s what that will look like:

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
43
44
45
46
47
48
49
50
@Configuration
@ComponentScan("com.braintreegateway")
public class BraintreeConfig {
     
    private static String CONFIG_FILENAME = "config.properties";
 
    @Bean
    public BraintreeGateway getBraintreeGateway() {
        Map<String,String> configMap = getMap();       
         
        BraintreeGateway gateway = null;
         
            try {
            gateway = BraintreeGatewayFactory.fromConfigMapping(configMap);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
         
        return gateway;
    }
     
     
    /**
     * Creates the map that we'll use to initialize the gateway
     * Reads values from the config file
     */
    private Map<String,String> getMap() {
        Map<String,String> map = new HashMap<String,String>();
         
        try {
            //get the input stream from the file in the resources folder
            Resource resource = new ClassPathResource(CONFIG_FILENAME);
            InputStream is = resource.getInputStream();
             
            //create a Properties object of key/value pairs
            Properties properties = new Properties();
            properties.load(is);
             
            //Special thanks to Java 8
            map.putAll(properties.entrySet().stream()
                    .collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString())));           
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
         
        return map;
    }
}

There are two annotations at the top of the code. They’re very important.

The @Configuration annotation tells Spring that the class is a configuration class. That means it can be used to define beans that will ultimately be injected into other objects. We’ll see how that works later.

The @ComponentScan annotation is necessary because the code will create a bean from an external library. In this case, it’s instantiating a BraintreeGateway object from the Braintree Java client library.

Note that the code is scanning the com.braintreegateway package. That’s the base package of the library.

At the top, the first line of code is a constant that defines the name and location of the configuration file. That file will include important info that Braintree needs, such as the public and private key.

In this case, the config file is named config.properties and located in the root of the resources folder.

config

Here’s the structure of the file:

1
2
3
4
BT_ENVIRONMENT=sandbox
BT_MERCHANT_ID=[YOUR MERCHANT ID]
BT_PUBLIC_KEY=[YOUR PUBLIC KEY]
BT_PRIVATE_KEY=[YOUR PRIVATE KEY]

The only public method in the class is annotated with @Bean. That’s because it’s used to create an object that can be managed by the Spring container and injected into other objects.

As you can see, getBraintreeGateway() is intuitively named and returns a BraintreeGateway object. It does that by calling the factory that we’ll look at in just a bit.

Before it can call BraintreeGatewayFactory, though, it needs to create a map. The code does that by reading the config file mentioned above and translating the name/value pairs in the file to key/value pairs in a Java Map object.

The second method, called getMap(), does most of the heavy lifting to make that happen.

 

The Factory

Next, let’s look at the class that creates the BraintreeGateway object.

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
public class BraintreeGatewayFactory {
    public static BraintreeGateway fromConfigMapping(Map<String, String> mapping) {
        return new BraintreeGateway(
            mapping.get("BT_ENVIRONMENT"),
            mapping.get("BT_MERCHANT_ID"),
            mapping.get("BT_PUBLIC_KEY"),
            mapping.get("BT_PRIVATE_KEY")
        );
    }
 
    public static BraintreeGateway fromConfigFile(File configFile) {
        InputStream inputStream = null;
        Properties properties = new Properties();
 
        try {
            inputStream = new FileInputStream(configFile);
            properties.load(inputStream);
        } catch (Exception e) {
            System.err.println("Exception: " + e);
        } finally {
            try { inputStream.close(); }
            catch (IOException e) { System.err.println("Exception: " + e); }
        }
 
        return new BraintreeGateway(
            properties.getProperty("BT_ENVIRONMENT"),
            properties.getProperty("BT_MERCHANT_ID"),
            properties.getProperty("BT_PUBLIC_KEY"),
            properties.getProperty("BT_PRIVATE_KEY")
        );
    }
}

There are two public, static methods. The first one creates the object from a Map.

Obviously, that’s the one used in this project.

The second method creates the object directly from the config file. That’s not used here.

If you’re wondering why the code uses the first method instead of the second one, it’s because the config file is embedded in the Spring JAR file. Spring has trouble reading File objects from its own JAR.

That’s why it’s best to read those kinds of files as Streams. Then, the code can process the Streams with a Reader or Properties object.

 

The Checkout Page

To keep things simple, there’s only one Controller class in this demo. Here’s the method that handles the main checkout page:

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping(value = "/checkouts", method = RequestMethod.GET)
public String checkout(Model model) {
    //get the token
    String clientToken = gateway.clientToken().generate();
     
    //add the token to the model - this will be used in JavaScript code
    model.addAttribute("clientToken", clientToken);
     
    //serve new.html
    return "newTransaction";
}

There’s nothing too fancy in that. The most important part is that the code invokes the BraintreeGateway object to create a client token. That token will be placed in the model so that the JavaScript code on the front end can use it to contact Braintree for payment processing.

Keep in mind the BraintreeGateway object was injected into the controller class:

1
2
@Autowired
private BraintreeGateway gateway;

The method closes by serving up newTransaction.html.

 

Checkout Page HTML Code

Next, let’s look at the important parts of the HTML on the main checkout page. Here’s some of that code towards the top:

1
2
3
4
5
6
7
8
9
<div th:if="${errorDetails}" class="note note-danger" id="serverSideErrorDiv">
 <h4 class="block">Error</h4>
 <div id="serverSideErrorText" th:text="${errorDetails}"></div>
</div>
                                 
<div class="note note-danger" id="errorDiv" style="display:none">
 <h4 class="block">Error</h4>
 <div id="errorText"></div>
</div>

Both of those div tags are designed to hold error messages. The top one holds server-side error messages and the bottom one holds client-side error messages.

Below that, there’s a table that looks like a shopping cart. Most of the info is hard-coded. Here’s the default output:

cart

As you can see, the only field that users can change is the price.

Here’s what the code for that table looks like:

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
<div class="table-container">
   <form id="payment-form" method="post" action="/checkouts">
    <table class="table table-striped table-bordered table-hover table-checkable" id="datatable_orders">
        <thead>
            <tr role="row" class="heading">
                <th width="15%"> Order&nbsp;# </th>
                <th width="15%"> Purchased&nbsp;On </th>
                <th width="55%"> Product </th>
                <th width="15%"> Price </th>
            </tr>
        </thead>
        <tbody>
           <tr role="row" class="filter">
                <td>12345678</td>
                <td>01/01/2018</td>
                <td>Blue Light Saber</td>
                <td>
                    <div class="margin-bottom-5">
                        <div class="input-group input-group-sm">
                           <span class="input-group-addon" id="sizing-addon1">$</span>
                           <input type="text" class="form-control form-filter input-sm margin-bottom-5 clearfix" maxlength="6" name="amount" id="amount" th:value="${amount != null} ? ${amount} : '10.00'" />
                        </div>
                    </div>
                </td>
            </tr>
        </tbody>
    </table>
   ...
   </form>
</div>

Note that the table is encapsulated in a <form> tag. That’s because of the single element that accepts input.

After the table code, there’s a placeholder for the Braintree drop-in.

What’s the Braintree drop-in? It’s the UI that Braintree’s JavaScript library will insert into the web page.

Here’s what the placeholder code looks like:

1
2
3
<div class="bt-drop-in-wrapper">
 <div id="bt-dropin"></div>
</div>

Finally, there’s the JavaScript portion of the checkout page:

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
<script th:inline="javascript">
    /*<![CDATA[*/
    var form = document.querySelector('#payment-form');
    var client_token = ;
 
    braintree.dropin.create({
      authorization: client_token,
      container: '#bt-dropin',
      paypal: {
        flow: 'vault'
      }
    }, function (createErr, instance) {
      form.addEventListener('submit', function (event) {
          event.preventDefault();
          $('#errorDiv').hide();
          $('#serverSideErrorDiv').hide();
           
          instance.requestPaymentMethod(function (err, payload) {
            if (err) {
              console.log('Error', err);
              showError(err);
              return;
            }
     
            // Add the nonce to the form and submit
            document.querySelector('#nonce').value = payload.nonce;
            form.submit();
          });
      });
    });
     
    function showError(err) {
        var message = String(err);
        $('#errorText').html(message);
        $('#errorDiv').show();
    }
    /*]]>*/
</script>

As you can see, the page starts off by referencing an external JavaScript library. That’s the code responsible for handling the drop-in.

Following that, you’ll see some page-specific JavaScript code. Most of it is dependent on the Braintree JavaScript library.

The code is blocked off with /*<[CDATA[*/ because the project is using Thymeleaf as its template engine of choice. Since Thymeleaf uses XHTML (instead of HTML), it’s a little fussier about what can and can’t be included in the code. Sometimes JavaScript causes problems with Thymeleaf, hence the CDATA designation.

The first line in the JavaScript code creates a form object from the contents of the form element.

The next line creates the client token. As you can see, it sets the client_token variable to the value of the clientToken string put in the model by the controller class. Check out the controller code above to see how that happens.

Next, the code invokes the create() method from the braintree.dropin object.

Two important things to note here. First, the braintree.dropin object was created with the inclusion of the Braintree JavaScript library identified above.

Next, the create() method is the workhorse method that plugs the payment widget into the page. As you might have guessed, it puts it in that placeholder element that you saw above.

The create() method accepts two parameters: an array object and a callback function.

The array object contains a series of name/value pairs that are fairly intuitive. For example, the “container” name is assigned to the element ID of the div where the drop-in will appear. In this case, it’s set to “#bt-dropin” because “bt-dropin” is the ID of the placeholder div tag.

The second parameter is the callback function. That’s the JavaScript method that gets executed once the user submits the page.

As you can see from above, the code interrupts the submit function on the form. Then, it hides the two error <div> elements.

After that, the code calls the requestPaymentMethod() function on the drop-in instance object.  That method accepts one parameter: a JavaScript function.

That function sets the nonce and submits the form.

If you’re unfamiliar with the word “nonce,” it’s a secure, single reference to payment info.

The showError() function in that JavaScript block shows a client-side JavaScript error. If you look at the instance.requestPaymentMethod() block just above, you’ll see that the callback function checks for the existence of err. If that exists, then it contains error info that the user needs to see. That’s why that block of code invokes the showError() function.

Now, let’s look at what happens when that form gets submitted.

 

POSTing the Checkout Form

Here’s the method that handles form submission on the checkout page:

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
43