Broadleaf Microservices
  • v1.0.0-latest-prod

Braintree Payment Integration

Frontend Responsibilities & Flow

  1. The frontend interaction begins with generating a Client Token by making a call to Broadleaf’s PaymentTransactionServices. If the customer is logged in, the Client Token is created with the customer’s id, allowing them to access any saved payment methods.

  2. The Client Token is then provided to the Braintree Drop-In JS library to initialize the UI.

  3. When this form is submitted, the payment method data is submitted directly to Braintree. In response, Braintree provides a nonce - i.e. a PCI-safe representation of the payment method.

  4. From there, the nonce should be stored in a PaymentTransactionServices Payment object in preparation for executing transactions.

Frontend Project Setup

<script src="https://js.braintreegateway.com/web/dropin/1.37.0/js/dropin.min.js"></script>

Example Integration

import { FC, useEffect, useState } from 'react';

import {
  Form as FormikForm,
  Formik,
  DefaultPaymentType,
} from 'formik';

import {
  useSubmitPaymentRequest,
} from '@broadleaf/payment-react';

import { CardType } from '@broadleaf/payment-js';

import { useGetTokenRequest } from '@broadleaf/braintree-payment-services-react';
import { GetTokenResponse } from '@broadleaf/braintree-payment-services-api';


let dropinInstanceResult;

export const BraintreePaymentForm: BraintreePaymentFormType = () => {

  const { error, onSubmit } = useHandleSubmit();

  const [getTokenResponse, setGetTokenResponse] =
    useState<GetTokenResponse>(null);

    // the BraintreePaymentServicesClient from '@broadleaf/braintree-payment-services-api'
  const braintreePaymentServicesClient = ...;
    // the AuthState from '@broadleaf/payment-js'
  const authState = ...;


  const {
    isFetching,
    getToken,
    error: getTokenError,
  } = useGetTokenRequest({braintreePaymentServicesClient: braintreePaymentServicesClient, authState});

  const braintree = window['braintree'];

  useEffect(() => {
    if (braintree) {
      if (!getTokenResponse) {
        getToken().then(response => {
          setGetTokenResponse(response);
        });
      } else {
        const config = {
          //Set Client Token generated by server and send it to the Braintree
          authorization: getTokenResponse.data.createClientToken.clientToken,
          container: '#dropin-container',
          /* For 3DS on Client Side need to uncomment next line */
          // threeDSecure: true,
          /* For the PayPal payment need to uncomment next code */
          // paypal: {
          //   flow: 'checkout',
          //   currency: currency,
          //   amount: amount
          // }
          /* Use this property for disable Credit Card form */
          // card: false,
          /* Priority for the payment options in the UI */
          // paymentOptionPriority: ['paypal', 'card'],
        };
        //Call to the Braintree and receive Nonce in the response
        braintree.dropin.create(config, (error, dropinInstance) => {
          if (error) console.error(error);
          dropinInstanceResult = dropinInstance;
        });
      }
    }
  }, [braintree, getTokenResponse, getToken]);



  if (isFetching || !getTokenResponse) {
    return null;
  }

  return (
    <Formik
      initialValues={...}
      onSubmit={onSubmit}
      validateOnBlur
      validateOnChange={false}
    >
      {() => (
        <FormikForm>
          <div id="braintree-card-element"></div>

          {(error || getTokenError) && (
            <strong className="block my-4 text-red-600 text-lg font-normal">
              There was an error processing your request. Please check your info and try again.
            </strong>
          )}
          <button type="submit">Submit</button>
        </FormikForm>
      )}
    </Formik>
  );
};


const useHandleSubmit = () => {
  // resolve cart
  const { cart, setCart } ...;
  // load the existing payments
  const { payments }  = ...;
  // the PaymentClient from '@broadleaf/commerce-cart'
  const paymentClient = ...;
  // the AuthState from '@broadleaf/payment-js'
  const authState = ...;

  const { handleSubmitPaymentInfo } = useSubmitPaymentRequest({
    authState,
    payments,
    ownerId: cart?.id,
    owningUserEmailAddress: cart.emailAddress,
    paymentClient,
    multiplePaymentsAllowed: false,
    rejectOnError: true,
  });

  const [error, setError] = useState(null);


  const onSubmit = async (data, actions): Promise<void> => {
    const { savePaymentForFutureUse, ...billingAddress } = data;

  const { handleSubmitPaymentInfo } = useSubmitPaymentRequest({
    authState,
    payments,
    ownerId: cart?.id,
    owningUserEmailAddress: cart.emailAddress,
    paymentClient,
    multiplePaymentsAllowed: false,
    rejectOnError: true,
  });

    const [error, setError] = useState(null);

    const paymentMethodProperties = {
      nonce: ''
    };

    // These parameters should include customer and transaction data. Details: https://developer.paypal.com/braintree/docs/guides/3d-secure/client-side
    // var threeDSecureParameters = {...};
    let displayAttributes: Record<string, string> = {};
    let paymentName;
    let paymentType;
      try {
        if (dropinInstanceResult) {
          const braintreeSubmitResponse =

            await dropinInstanceResult.requestPaymentMethod({
            /* Use this parameter for 3DS payment */
            // threeDSecure: threeDSecureParameters
            });
          //Set Nonce to the paymentProperties
          paymentMethodProperties.nonce = braintreeSubmitResponse.nonce;

          // cardBrand will be one of the following values: Master, Visa, Mada, Amex
          const creditCardType =
            braintreeSubmitResponse.details.cardType.toUpperCase();

          displayAttributes = {
            creditCardType,
          };
          paymentName = braintreeSubmitResponse.details.cardType;

          paymentType =
          braintreeSubmitResponse.type === 'CreditCard'
            ? DefaultPaymentType.CREDIT_CARD
            : 'PAYPAL';
        }
      } catch (err) {
        console.error(err);
        setError(err);

        return;
      }

    const paymentRequest = {
      name: paymentName,
      type: paymentType,
      gatewayType: 'BRAINTREE',
      amount: { amount: 11, currency: 'USD' },
      subtotal: cart.cartPricing.subtotal,
      adjustmentsTotal: cart.cartPricing.adjustmentsTotal,
      fulfillmentTotal: cart.cartPricing.fulfillmentTotal,
      taxTotal: cart.cartPricing.totalTax,
      billingAddress,
      shouldSavePaymentForFutureUse: savePaymentForFutureUse,
      paymentMethodProperties,
      displayAttributes,
    } as PaymentRequest;


    try {
      const paymentSummary = await handleSubmitPaymentInfo(paymentRequest);

    } catch (err) {
      setError(err);
    } finally {
      ...
    }
  };

  return { error, onSubmit };
};

Get Client Token End Point integration with front end.

  1. Braintree provide getClientToken endpoint for generating token with unique token. This client token is unique for each payment executing in braintree. Once client token is used we can’t use that same client token for executing the another transaction request.Here is the link for getClientToken endpoint of braintree.

  2. On load of payment page we are making call to the getClientToken endpoint and get the unique token from braintree and store that same token on payment object at broadleaf.

  3. GetClientToken endpoint is registered in PaymentTransactionService of broadleaf.