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
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
echo -n "v4ndq314c2s5c28r:93b78bc88be90d93ac282e50ae569fdd" | base64
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:
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:
- Your server calls the Braintree GraphQL API to create a client token which should be sent to the client (your web or mobile app).
- 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.
- The client sends the payment method ID to your server.
- 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.
Secure your secrets conveniently
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.
Requesting a Client Token
Let's bootstrap a new Next.js project so we can start integrating with Braintree:
npx create-next-app nextjs-braintree --ts --use-npm
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.
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:
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
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:
const apiKey = await fetchBraintreeApiKey()
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:
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
}
Next, we need to execute the GraphQL mutation
mutation {
createClientToken {
clientToken
}
}
Let's define this as a string constant in a new src/braintreeApi/graphql.ts
file:
export const CREATE_CLIENT_TOKEN_MUTATION = `
mutation createClientToken($input: CreateClientTokenInput) {
createClientToken(input: $input) {
clientToken
}
}`.trim()
Let's also define a TypeScript type for the response we expect the mutation to return:
export interface CreateClientTokenResponse {
data: {createClientToken: {clientToken: string}}
}
Now we have all the building blocks necessary to make the GraphQL call in getServerSideProps
:
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
This code makes use of a few constants which can be defined in braintreeApi/constants.ts
:
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
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 extracted from the client token like so
const {authorizationFingerprint} = JSON.parse(atob(clientToken))
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
mutation tokenizeCreditCard($input: TokenizeCreditCardInput!) {
tokenizeCreditCard(input: $input) {
paymentMethod {
id
}
}
}
The exact data required to form a TokenizeCreditCardInput
can be determined from Braintree's GraphQL Explorer , specifically the "Docs" sidebar on the right.
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:
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,
}
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.
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)
}
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:
mutation chargePaymentMethod($input: ChargePaymentMethodInput!) {
chargePaymentMethod(input: $input) {
transaction {
id
status
}
}
}
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:
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.')
}
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
Transaction dHJhbnNhY3Rpb25fY2t2bWsycjI created successfully. Status = SUBMITTED_FOR_SETTLEMENT
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.
Other articles
The Quickest Way to Set Up Stripe in a Web App
This article shows how to get started using Stripe to accept payments in a Next.js application, using the Zero TypeScript SDK to fetch the Stripe API key.
Use Pusher to Implement Real-Time Notifications
Pusher makes it easy to add pub-sub functionality to your web apps, allowing you to implement chat, notifications, and more.
Secure your secrets
Zero is a modern secrets manager built with usability at its core. Reliable and secure, it saves time and effort.