Zero
Zero
Back

The Complete Guide to Getting Started with the Braintree GraphQL API

Braintree is one of the world's leading payment platforms. This article provides an in-depth guide to getting started with Braintree, from getting your API token all the way to collecting customer payment information and charging a credit card.

Sam Magura

Sam Magura

Old cash register

Braintree  is one of the world's leading payment platforms. On a basic level, Braintree allows you to charge customers from your web app. The service is owned by PayPal and supports a wide array of payment methods — card, PayPal, Venmo, Apple Pay, and Google Pay — so your customers can check out with ease no matter which payment method they prefer.

Braintree provides a powerful and modern GraphQL API to enable you to quickly integrate with the services' many features without having to add any dependencies to your project. This article will guide you through implementing a basic payment use case — charging a credit card — using the Braintree GraphQL API from Node.js and React, specifically Next.js . That said, it is straightforward to adapt the code shown in this guide to any web app tech stack. That's the beauty of integrating via an HTTP-based API rather than a language-specific SDK.

Security is of the utmost importance whenever financial data is involved, so it's critical that we handle our Braintree API key safely. That's why we will store the Braintree API key in the Zero secrets manager and fetch it at runtime using the official Zero TypeScript SDK. If using a language other than JavaScript or TypeScript, you can use one of Zero's other SDKs , or interface with Zero's GraphQL API  directly.

🔗 The full code for this example is available in the zerosecrets/examples  GitHub repository.

Signing Up for Braintree

To start using Braintree, you'll need to sign up  for a Sandbox account. The Sandbox allows you to develop against the API with zero risk of making a real charge.

Once logged in, you will be presented with your public key, private key, and merchant ID. If not, click the gear icon in the upper right corner of the page and select "API". Your API key is the public key followed by the private key encoded in base 64. You can easily obtain your API key by running

1
echo -n "v4ndq314c2s5c28r:93b78bc88be90d93ac282e50ae569fdd" | base64
shell

Just remember to replace v4ndq314c2s5c28r with your public key and 93b78bc88be90d93ac282e50ae569fdd with your private key.

⚠️ Both your private key and API key provide access to your account, so they must be kept secure. Be careful not to commit either of these values to a git repository or access them on the client side.

Server-side calls to the GraphQL API use the API key (not the private key), so it's the API key which we need to copy into Zero:

Adding a Braintree secret in Zero

A High-Level Overview of Accepting a Payment

The simplest way to use Braintree is to accept a one-time payment. The excellent Braintree docs  explain how this process works at a technical level:

  1. Your server calls the Braintree GraphQL API to create a client token which should be sent to the client (your web or mobile app).
  2. The client collects payment information from the user (e.g. credit card number) and passes it to Braintree along with the client token to obtain a payment method ID.
  3. The client sends the payment method ID to your server.
  4. The server creates a transaction in Braintree via the payment method ID.

This design is great because the customer's payment information never passes through your server. This sidesteps a whole range of serious security and privacy issues that can arise when handling sensitive financial information.

Requesting a Client Token

Let's bootstrap a new Next.js project so we can start integrating with Braintree:

1
npx create-next-app nextjs-braintree --ts --use-npm
shell

Our app will contain a single page with a "Submit fake payment" button that submits a hardcoded credit card and billing address to the server. When the server receives the request, it creates a transaction in Braintree and returns the transaction ID and status to the client.

The simple app we're building to test out Braintree

As described in the previous section, the first step is to request a client token . This requires your Braintree API key, so the request must be made on the server side. We can add server-side logic to a Next.js page by defining a getServerSideProps function in the same file as the page:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Props {
  clientToken: string
}

export const getServerSideProps: GetServerSideProps<Props> = async () => {
  // TODO Get the client token from the GraphQL API

  return {props: {clientToken}}
}

const Home: NextPage<Props> = ({clientToken}) => {
  // TODO Render the UI
}

export default Home
typescript

With the general structure of the code in place, let's work on filling in getServerSideProps. First, we'll need to fetch the Braintree API key from Zero:

1
const apiKey = await fetchBraintreeApiKey()
typescript

The API key will be used in multiple different places in the app, so it's best to create a fetchBraintreeApiKey helper function so that the code for calling Zero is not duplicated. The function uses the Zero TypeScript SDK to exchange your Zero token for the Braintree API key:

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
import {zero} from '@zerosecrets/zero'

let apiKey: string | undefined

export async function fetchBraintreeApiKey(): Promise<string> {
  // Don't call Zero if we already fetched the API key
  if (apiKey) {
    return apiKey
  }

  if (!process.env.ZERO_TOKEN) {
    throw new Error('Did you forget to set the ZERO_TOKEN environment variable?')
  }

  const secrets = await zero({
    token: process.env.ZERO_TOKEN,
    pick: ['braintree'],
  }).fetch()

  if (!secrets.braintree) {
    throw new Error('Did not receive an API key for Braintree.')
  }

  apiKey = secrets.braintree.API_KEY

  return apiKey
}
typescript

Next, we need to execute the GraphQL mutation

1
2
3
4
5
mutation {
  createClientToken {
    clientToken
  }
}
graphql

Let's define this as a string constant in a new src/braintreeApi/graphql.ts file:

1
2
3
4
5
6
export const CREATE_CLIENT_TOKEN_MUTATION = `
mutation createClientToken($input: CreateClientTokenInput) {
  createClientToken(input: $input) {
    clientToken
  }
}`.trim()
typescript

Let's also define a TypeScript type for the response we expect the mutation to return:

1
2
3
export interface CreateClientTokenResponse {
  data: {createClientToken: {clientToken: string}}
}
typescript

Now we have all the building blocks necessary to make the GraphQL call in getServerSideProps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const body = {
  query: CREATE_CLIENT_TOKEN_MUTATION,
  variables: {
    input: {
      clientToken: {
        merchantAccountId: BRAINTREE_MERCHANT_ID,
      },
    },
  },
}

// Node 18+ required for built-in fetch support
const response = await fetch(BRAINTREE_GRAPHQL_ENDPOINT, {
  method: 'POST',
  body: JSON.stringify(body),
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Basic ${apiKey}`,
    'Braintree-Version': BRAINTREE_VERSION,
  },
})
const {data} = (await response.json()) as CreateClientTokenResponse
const clientToken = data.createClientToken.clientToken
typescript

This code makes use of a few constants which can be defined in braintreeApi/constants.ts:

1
2
3
4
5
6
7
export const BRAINTREE_GRAPHQL_ENDPOINT = 'https://payments.sandbox.braintree-api.com/graphql'

// Set this to the date you started using the Braintree API
export const BRAINTREE_VERSION = '2022-09-29'

// TODO: Set NEXT_PUBLIC_BRAINTREE_MERCHANT_ID to your merchant ID in a .env file
export const BRAINTREE_MERCHANT_ID = process.env.NEXT_PUBLIC_BRAINTREE_MERCHANT_ID
typescript

If you run the project with ZERO_TOKEN='your-zero-token' npm run dev, you should get a long string like eyJ2ZXJzaW9uIjoyLCJhdXRob3JpemF0... for the client token.

The client token is actually a base 64 encoded JSON string which contains an authorization fingerprint. The fingerprint can be extract from the client token like so

1
const {authorizationFingerprint} = JSON.parse(atob(clientToken))
typescript

It is this fingerprint that must be passed in the Authorization header when making GraphQL requests on the client side.

Collecting Payment Information

Now that the frontend has an authorization fingerprint, the next step is to collect the user's payment information and exchange it for a token (payment method ID) using the mutation

1
2
3
4
5
6
7
mutation tokenizeCreditCard($input: TokenizeCreditCardInput!) {
  tokenizeCreditCard(input: $input) {
    paymentMethod {
      id
    }
  }
}
graphql

The exact data required to form a TokenizeCreditCardInput can be determined from Braintree's GraphQL Explorer , specifically the "Docs" sidebar on the right.

TokenizeCreditCardInput in the Braintree GraphQL Explorer

TypeScript types should be added to braintreeApi/graphql.ts to represent TokenizeCreditCardInput and the input types it references, such as CreditCardInput and AddressInput.

In a real-world application, the UI would display a form where the user can enter their credit card information and billing address. To keep this demonstration simple, we'll simply hardcode a fake billing address and credit card:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const billingAddress: AddressInput = {
  addressLine1: '2435 Lynn Rd',
  addressLine2: 'Suite 206',
  locality: 'Raleigh',
  region: 'NC',
  postalCode: '27612',
  countryCode: 'US',
}

const creditCard: CreditCardInput = {
  number: '4242424242424242',
  expirationYear: '25',
  expirationMonth: '12',
  cvv: '001',
  cardholderName: 'Samuel Magura',
  billingAddress: billingAddress,
}
typescript

When the user clicks the "Submit fake payment" button in the UI, we'll call the tokenizeCreditCard mutation with the hardcoded credit card. This returns a payment method ID which we pass to our backend, which will use the payment method ID to charge the credit card.

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
const submit: React.FormEventHandler = async (e) => {
  e.preventDefault()

  const {authorizationFingerprint} = JSON.parse(atob(clientToken))

  const input: TokenizeCreditCardInput = {creditCard}

  const body = {
    query: TOKENIZE_CREDIT_CARD_MUTATION,
    variables: {input},
  }

  const tokenizeCreditCardResponse = await fetch(BRAINTREE_GRAPHQL_ENDPOINT, {
    method: 'POST',
    body: JSON.stringify(body),
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${authorizationFingerprint}`,
      'Braintree-Version': BRAINTREE_VERSION,
    },
  })
  const {data} = (await tokenizeCreditCardResponse.json()) as TokenizeCreditCardResponse
  const paymentMethodId = data.tokenizeCreditCard.paymentMethod.id

  const createTransactionResponse = await fetch('/api/createTransaction', {
    method: 'POST',
    body: JSON.stringify({paymentMethodId}),
    headers: {
      'Content-Type': 'application/json',
    },
  })

  setServerResponse((await createTransactionResponse.json()) as string)
}
typescript

Creating a Transaction

The only remaining step is to implement the /api/createTransaction method on the backend. In Next.js, you can create a backend API method by placing files in the src/pages/api directory, so let's create the file src/pages/api/createTransaction.ts.

The createTransaction API route should accept a payment method ID in the request body and use it to call the Braintree API with the following mutation:

1
2
3
4
5
6
7
8
mutation chargePaymentMethod($input: ChargePaymentMethodInput!) {
  chargePaymentMethod(input: $input) {
    transaction {
      id
      status
    }
  }
}
graphql

TypeScript types can be created for the input and response objects by using the GraphQL explorer as we did previously. The final code for the API route looks 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
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
try {
  if (req.method !== 'POST') {
    throw new Error('Method must be POST.')
  }

  const paymentMethodId = req.body.paymentMethodId

  if (typeof paymentMethodId !== 'string' || paymentMethodId.length === 0) {
    throw new Error('paymentMethodId was not provided.')
  }

  const apiKey = await fetchBraintreeApiKey()

  const transaction: TransactionInput = {
    amount: 10,
  }

  const chargePaymentMethodInput: ChargePaymentMethodInput = {
    paymentMethodId,
    transaction: transaction,
  }

  const body = {
    query: CHARGE_PAYMENT_METHOD_MUTATION,
    variables: {
      input: chargePaymentMethodInput,
    },
  }

  const response = await fetch(BRAINTREE_GRAPHQL_ENDPOINT, {
    method: 'POST',
    body: JSON.stringify(body),
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Basic ${apiKey}`,
      'Braintree-Version': BRAINTREE_VERSION,
    },
  })

  const {data} = (await response.json()) as ChargePaymentMethodResponse
  const transactionResponse = data.chargePaymentMethod.transaction

  res
    .status(200)
    .json(`Transaction ${transactionResponse.id} created successfully. ` + `Status = ${transactionResponse.status}`)
} catch (e) {
  res.status(500).json(e instanceof Error ? `${e.message}\n\n${e.stack}` : 'Unknown error.')
}
typescript

The string returned by this API method should be displayed in the UI so we know whether the integration with the Braintree Sandbox is working. If it worked, you'll see a string like

1
Transaction dHJhbnNhY3Rpb25fY2t2bWsycjI created successfully. Status = SUBMITTED_FOR_SETTLEMENT
shell

Congratulations, you just charged your first customer!

In Conclusion

Once you wrap your head around the multi-step process that is required to submit a transaction to Braintree, the rest is straightforward thanks to the documentation provided in Braintree's GraphQL explorer. While Braintree does provide a few ready-made SDKs, calling the GraphQL API directly is likely to be the most flexible approach. It also keeps your node_modules directory lean as it does not require any additional dependencies.