Braintree

Overview

This guide will help you integrate the Braintree payment gateway into your Chec/Commerce.js storefront using Braintree’s Hosted Fields checkout UI. Hosted Fields is a completely customizable UI which accepts credit card payments with PCI compliant security. This ensures the payment details that are collected in the checkout fields are secure and eligible for PCI compliance and SCA (strong customer authentication). Braintree Direct can also be used where there is support for a variety of other payment methods - PayPal, Apple Pay, Google Pay, and Venmo - without necessarily using a provided Hosted Fields checkout UI.

In this guide

  1. Overview of the payment flow between Chec/Commerce.js and Braintree
  2. Walk-through implementation with a step-by-step guide
    • Capturing an order with payment details entered into the Hosted Fields
    • Handling 3D secure payments (SCA), only when required
    • Sending the returned payment nonce to the Commerce.js “capture checkout” request

Payment flow

The first four steps are done client side in the frontend of your integration. Once these steps are completed, the last step will be to create the transaction to send to the Chec API.

Step by step

  1. The Chec API generates the Braintree client token and returns it in your checkout token. Alternatively, a tokenization key can be used.
  2. Initialize the client SDK by passing in the Braintree tokenization key or generated client token as the client authorization value.
  3. On the client side, the customer enters their payment details which is then sent to Braintree. A payment method “nonce” is subsequently returned back to the client. This is a random, short-lived token that represents your request.
  4. Your frontend receives the nonce and sends it to the Chec API.
  5. Chec API receives the nonce and uses it to create a transaction from the collected payment details. This exchange of customer data is completely safeguarded and keeps the payment process PCI compliant.

Example implementation

This implementation assumes that you are familiar with JavaScript concepts including making requests using try/catch and async/await. We will start the guide also assuming you have already:

  • signed up for a Braintree and Chec account
  • enabled Braintree in your Chec merchant account and input your Braintree merchant ID, public key, and secret key (you don’t need the tokenization key for this example)
  • installed the JavaScript client SDK, and
  • added Hosted Fields UI markup
Info

This Braintree tutorial explains the last 2 points to complete before we jump into the implementation.

Capturing an order with Braintree’s Hosted Fields UI

Once you are ready to capture an order (the customer has entered their card details in the Braintree hosted fields UI and clicked an action button to continue), you need to create the transaction to start processing the payment with Commerce.js’ checkout capture request.

import Commerce from '@chec/commerce.js';
import braintreeClient from 'braintree-web/client';
import hostedFields from 'braintree-web/hosted-fields'
import threeDSecure from 'braintree-web/three-d-secure';

// Create a commerce.js instance using your sandbox key
const commerce = new Commerce('pk_test_commercejsKey');

// Define your hosted fields and styles options to 
// pass into the `hostedFields.create` function
const hostedFieldsOptions = {
  styles: {
    'input.valid': {
      'color': '#8bdda8'
    },
    'input.invalid': {
      'color': '#ff5977',
    },
    ':focus': {
      'color': 'black'
    },
  },
  fields: {
    number: {
      selector: '#bt-card-number',
      placeholder: '4111 1111 1111 1111',
    },
    cvv: {
      selector: '#bt-cvv',
      placeholder: '123',
    },
    expirationDate: {
      selector: '#bt-expiration-date',
      placeholder: '10/2022',
    },
    postalCode: {
      selector: '#bt-postal-code',
      placeholder: '11111'
    },
  },
}

/**
 * Creates the Braintree hosted fields UI
 * Call this function after initializing the Braintree client
 */
const createBraintreeHostedFields = async (clientInstance) => {
  try {
    const createdHostedFields = await hostedFields.create({
      client: clientInstance,
      ...hostedFieldsOptions,
    });

    // Set your hosted fields instance state here

  } catch (err) {
    console.error('There was an error creating the hosted fields', err.message)
  }
}

/**
 * Initialize 3D Secure client
 * Call this function after your Braintree client is created
 */
const initializeThreeDSecureClient = async () => {
  try {
    // Create the 3D Secure client
    const threeDSecureClient = threeDSecure.create({
      authorization: '{CHEC_GENERATED_CLIENT_TOKEN}',
      version: 2,
    });

    // Set your 3D Secure instance state here

  } catch (err) {
    console.error('There was an error initializing the 3D Secure client', err.message);
  }
}

/**
 * Initialize Braintree client and create payment request
 * Call this function on page load to render the hosted fields UI
 */
const createBraintreePaymentRequest = async () => {
  try {
    // Create the Braintree client
    const client = await braintreeClient.create({
      authorization: '{CHEC_GENERATED_CLIENT_TOKEN}'
    });

    // Initialize 3D Secure client
    initializeThreeDSecureClient();

    // Create hosted fields instance with Braintree client to request payment
    createBraintreeHostedFields(client);
  // Handle initialization error
  } catch (err) {
    if (err.code === 'CLIENT_AUTHORIZATION_INVALID') {
            // either the client token has expired, and a new one should be generated
    // or the tokenization key was deactivated or deleted
    console.error('There was an error initializing the Braintree client', err.message);
    }
    return;
  }
}

// Call Braintree payment request function to initialize and mount the hosted fields UI on page load
createBraintreePaymentRequest();

In the above checkout component, we first imported the necessary Braintree modules, defined the Hosted Fields and 3D Secure instances in our state and customized the fields and styles options.

Next, we created three functions -

  • createBraintreeHostedFields() takes in the clientInstance as a parameter and creates the hosted fields by passing in the clientInstance and hostedFieldsOptions
  • initializeThreeDSecureClient() initializes the 3D secure component which we will use to handle 3D secure transactions
  • createBraintreePaymentRequest() is where we initialize the braintreeClient and create the payment request method using the created hosted fields

In both the initializeThreeDSecureClient() and createBraintreePaymentRequest() methods, it is required to pass in a client authorization. For this example integration, we are using the client token created by Chec and returned in the checkout token in the Braintree gateway object. Using the client token ensures that we can create a 3D Secure transaction as providing the tokenization key does not come with SCA protection. It was always advisable to use 3D Secure for any payment transactions as it adds an additional security layer and reduces your risk of fraud and charge. Read more on how Braintree’s 3D secure module handles SCA requirements.

After all the necessary methods are created, we call our createBraintreePaymentRequest() to render out the payment request UI on page load.

Lastly, in our captureOrder() function below, we call tokenize() on our hosted fields instance to receive a payment payload which contains the nonce. Next, we handle any errors using client side verification process. Once the payment nonce is verified, we will use it to call the final checkout capture request.

/**
 * Capture order with Braintree
 * Call this function when a "complete order" button is clicked
 */
const captureOrder = async () => {
  // This process includes a few API calls, so now is a good time to show a loading indicator

  // Use `tokenize` to get back a card payment nonce
  const paymentPayload = await hostedFieldsInstance.tokenize();

  // 3D secure verification is handled on the client side after calling
  // `tokenize` on the hosted fields instance. The various
  // errors handled here use 3D secure requirements
  // See: https://braintree.github.io/braintree-web/current/HostedFields.html#tokenize
  if (paymentPayload.error) {
    switch (paymentPayload.error.code) {
      case 'HOSTED_FIELDS_FIELDS_EMPTY':
        // Occurs when none of the fields are filled in
        console.error('All fields are empty! Please fill out the form.');
        break;
      case 'HOSTED_FIELDS_FIELDS_INVALID':
        // Occurs when certain fields do not pass client side validation
        console.error('Some fields are invalid:', paymentPayload.error.details.invalidFieldKeys);
      case 'HOSTED_FIELDS_TOKENIZATION_CVV_VERIFICATION_FAILED':
        // Occurs when the cvv does not pass verification (https://developers.braintreepayments.com/reference/general/testing/#avs-and-cvv/cid-responses)
        // See: https://developers.braintreepayments.com/reference/request/client-token/generate/#options.verify_card
        console.error('CVV did not pass verification');
        break;
      case 'HOSTED_FIELDS_FAILED_TOKENIZATION':
        // occurs for any other tokenization error on the server
        console.error('Tokenization failed server side. Is the card valid?');
        break;
      default:
        console.error('Something bad happened!', paymentPayload.error);
    }
    return;
  }

  // Once the payment details pass neccessary verification and 
  // the nonce is received, continue with capturing checkout with Commerce
  try {
    // Use a checkout token ID, and the checkout details that may have been collected
    // on this page. Note that Commerce.js checkout tokens may already have all the information saved against them to
    // capture an order, so this extra detail may be optional.
    const newOrder = await commerce.checkout.capture(checkoutTokenId, {
      ...checkoutPayload,
      // Include the returned nonce from Braintree
      payment: {
        gateway: 'braintree',
        braintree: {
          nonce: paymentPayload.nonce,
        },
      },
    });

    // Handle order success here
    console.log(order);

    // Set processing to false here

  } catch (resp) {
    // Order failure with Commerce.js is caught here
    // Set error here
    console.log(resp);
    // Set loading to false here
  }
}

The example implementation includes most of the basic features you will need to capture payments using Braintree with 3D Secure protection. The Braintree SDK comes with other modules and actions you could also hook in to your checkout process, check out the Braintree web client reference for other modules documentation.