title: "BraintreePlugin" isDefaultIndex: false
import MemberInfo from '@site/src/components/MemberInfo'; import GenerationInfo from '@site/src/components/GenerationInfo'; import MemberDescription from '@site/src/components/MemberDescription';
This plugin enables payments to be processed by Braintree, a popular payment provider.
Then install braintree and @types/braintree from npm. This plugin was written with v3.x of the Braintree lib.
yarn add @vendure/payments-plugin braintree
yarn add -D @types/braintree
or
npm install @vendure/payments-plugin braintree
npm install -D @types/braintree
Add the plugin to your VendureConfig plugins array:
import { BraintreePlugin } from '@vendure/payments-plugin/package/braintree';
import { Environment } from 'braintree';
// ...
plugins: [
BraintreePlugin.init({
environment: Environment.Sandbox,
// This allows saving customer payment
// methods with Braintree (see "vaulting"
// section below for details)
storeCustomersInBraintree: true,
}),
]
Create a new PaymentMethod in the Admin UI, and select "Braintree payments" as the handler.
Fill in the Merchant ID, Public Key & Private Key from your Braintree sandbox account.
The plugin is designed to work with the Braintree drop-in UI. This is a library provided by Braintree which will handle the payment UI for you. You can install it in your storefront project with:
yarn add braintree-web-drop-in
# or
npm install braintree-web-drop-in
The high-level workflow is:
generateBraintreeClientToken mutation which is exposed by this plugin."paymentMethodRequestable" event which emitted by the Dropin.requestPaymentMethod() method to get the required payment metadata.addPaymentToOrder mutation. The metadata should be an object of type { nonce: string; }Here is an example of how your storefront code will look. Note that this example is attempting to be framework-agnostic, so you'll need to adapt it to fit to your framework of choice.
// The Braintree Dropin instance
let dropin: import('braintree-web-drop-in').Dropin;
// Used to show/hide a "submit" button, which would be bound to the
// `submitPayment()` method below.
let showSubmitButton = false;
// Used to display a "processing..." spinner
let processing = false;
//
// This method would be invoked when the payment screen is mounted/created.
//
async function renderDropin(order: Order, clientToken: string) {
// Lazy load braintree dropin because it has a reference
// to `window` which breaks SSR
dropin = await import('braintree-web-drop-in').then((module) =>
module.default.create({
authorization: clientToken,
// This assumes a div in your view with the corresponding ID
container: '#dropin-container',
card: {
cardholderName: {
required: true,
},
overrides: {},
},
// Additional config is passed here depending on
// which payment methods you have enabled in your
// Braintree account.
paypal: {
flow: 'checkout',
amount: order.totalWithTax / 100,
currency: 'GBP',
},
}),
);
// If you are using the `storeCustomersInBraintree` option, then the
// customer might already have a stored payment method selected as
// soon as the dropin script loads. In this case, show the submit
// button immediately.
if (dropin.isPaymentMethodRequestable()) {
showSubmitButton = true;
}
dropin.on('paymentMethodRequestable', (payload) => {
if (payload.type === 'CreditCard') {
showSubmitButton = true;
}
if (payload.type === 'PayPalAccount') {
this.submitPayment();
}
});
dropin.on('noPaymentMethodRequestable', () => {
// Display an error
});
}
async function generateClientToken() {
const { generateBraintreeClientToken } = await graphQlClient.query(gql`
query GenerateBraintreeClientToken {
generateBraintreeClientToken
}
`);
return generateBraintreeClientToken;
}
async submitPayment() {
if (!dropin.isPaymentMethodRequestable()) {
return;
}
showSubmitButton = false;
processing = true;
const paymentResult = await dropin.requestPaymentMethod();
const { addPaymentToOrder } = await graphQlClient.query(gql`
mutation AddPayment($input: PaymentInput!) {
addPaymentToOrder(input: $input) {
... on Order {
id
payments {
id
amount
errorMessage
method
state
transactionId
createdAt
}
}
... on ErrorResult {
errorCode
message
}
}
}`, {
input: {
method: 'braintree', // The code of you Braintree PaymentMethod
metadata: paymentResult,
},
},
);
switch (addPaymentToOrder?.__typename) {
case 'Order':
// Adding payment succeeded!
break;
case 'OrderStateTransitionError':
case 'OrderPaymentStateError':
case 'PaymentDeclinedError':
case 'PaymentFailedError':
// Display an error to the customer
dropin.clearSelectedPaymentMethod();
}
}
Braintree has a vault feature which allows the secure storage of customer's payment information. Using the vault allows you to offer a faster checkout for repeat customers without needing to worry about how to securely store payment details.
To enable this feature, set the storeCustomersInBraintree option to true.
BraintreePlugin.init({
environment: Environment.Sandbox,
storeCustomersInBraintree: true,
}),
Since v1.8, it is possible to override vaulting on a per-payment basis by passing includeCustomerId: false to the generateBraintreeClientToken
mutation:
const { generateBraintreeClientToken } = await graphQlClient.query(gql`
query GenerateBraintreeClientToken($includeCustomerId: Boolean) {
generateBraintreeClientToken(includeCustomerId: $includeCustomerId)
}
`, { includeCustomerId: false });
as well as in the metadata of the addPaymentToOrder mutation:
const { addPaymentToOrder } = await graphQlClient.query(gql`
mutation AddPayment($input: PaymentInput!) {
addPaymentToOrder(input: $input) {
...Order
...ErrorResult
}
}`, {
input: {
method: 'braintree',
metadata: {
...paymentResult,
includeCustomerId: false,
},
}
);
class BraintreePlugin {
static options: BraintreePluginOptions = {};
init(options: BraintreePluginOptions) => Type<BraintreePlugin>;
}
Options for the Braintree plugin.
interface BraintreePluginOptions {
environment?: Environment;
storeCustomersInBraintree?: boolean;
extractMetadata?: (transaction: Transaction) => PaymentMetadata;
}