Payment Integrations

Braintree Payment Integration: Drop-in and Custom Approaches

Complete Braintree integration covering Drop-in UI, hosted fields, PayPal and Venmo, vault for recurring payments, and production deployment patterns for marketplace and subscription businesses.

Khalid Aboubakr
24 min read
BraintreePaypalPayment GatewayReactNodejsDrop InVenmoVault

Table of Contents

  1. Why Braintree
  2. Server-Side Setup
  3. Drop-in UI Integration
  4. Hosted Fields for Custom UI
  5. PayPal and Venmo Integration
  6. Vault for Recurring Payments
  7. Webhook Configuration
  8. Production Deployment

Why Braintree

Braintree (owned by PayPal) offers unique advantages:

  • PayPal native integration - Seamless PayPal checkout
  • Venmo support - US mobile wallet
  • Vault - Token storage for recurring payments
  • Marketplace support - Split payments out of the box
  • Global reach - 45+ countries, 130+ currencies

I've used Braintree for marketplace platforms and subscription businesses where PayPal integration is critical.

Server-Side Setup

Configuration

// config/braintree.ts import braintree from 'braintree'; export const gateway = new braintree.BraintreeGateway({ environment: process.env.NODE_ENV === 'production' ? braintree.Environment.Production : braintree.Environment.Sandbox, merchantId: process.env.BRAINTREE_MERCHANT_ID!, publicKey: process.env.BRAINTREE_PUBLIC_KEY!, privateKey: process.env.BRAINTREE_PRIVATE_KEY!, }); export const BRAINTREE_CONFIG = { merchantId: process.env.BRAINTREE_MERCHANT_ID!, tokenizationKey: process.env.BRAINTREE_TOKENIZATION_KEY!, };

Payment Service

// services/braintree/PaymentService.ts import { gateway } from '@/config/braintree'; export class BraintreePaymentService { async generateClientToken(customerId?: string): Promise<string> { const options: braintree.ClientTokenRequest = {}; if (customerId) { // For returning customers with vaulted payment methods options.customerId = customerId; } const response = await gateway.clientToken.generate(options); return response.clientToken; } async createTransaction(params: { paymentMethodNonce: string; amount: number; orderId: string; customerId?: string; storeInVault?: boolean; deviceData?: string; }): Promise<{ success: boolean; transactionId?: string; errorMessage?: string; }> { const { paymentMethodNonce, amount, orderId, customerId, storeInVault, deviceData, } = params; // Validate order const order = await this.orderRepo.findById(orderId); if (!order || Math.abs(order.total - amount) > 0.01) { throw new PaymentError('INVALID_ORDER', 'Order validation failed'); } try { const transactionRequest: braintree.TransactionRequest = { amount: amount.toFixed(2), paymentMethodNonce, orderId, options: { submitForSettlement: true, }, }; // Store in vault for future payments if (storeInVault && customerId) { transactionRequest.customerId = customerId; transactionRequest.options!.storeInVaultOnSuccess = true; } // Device data for fraud protection if (deviceData) { transactionRequest.deviceData = deviceData; } const result = await gateway.transaction.sale(transactionRequest); if (result.success) { await this.paymentRepo.create({ orderId, braintreeTransactionId: result.transaction.id, amount, status: 'paid', paymentMethod: result.transaction.paymentInstrumentType, }); await this.orderService.fulfillOrder(orderId); return { success: true, transactionId: result.transaction.id, }; } else { const errorMessage = result.message || 'Transaction failed'; await this.paymentRepo.create({ orderId, status: 'failed', failureReason: errorMessage, }); return { success: false, errorMessage, }; } } catch (error) { console.error('Braintree transaction failed:', error); throw new PaymentError('TRANSACTION_FAILED', 'Payment processing failed'); } } async refundTransaction( transactionId: string, amount?: number ): Promise<{ success: boolean; refundId?: string }> { const result = amount ? await gateway.transaction.refund(transactionId, amount.toFixed(2)) : await gateway.transaction.refund(transactionId); return { success: result.success, refundId: result.transaction?.id, }; } }

Drop-in UI Integration

React Component

// components/payment/BraintreeDropIn.tsx import { useEffect, useRef, useState, useCallback } from 'react'; import dropin, { Dropin } from 'braintree-web-drop-in'; interface BraintreeDropInProps { clientToken: string; amount: number; orderId: string; onSuccess: (result: any) => void; onError: (error: any) => void; } export function BraintreeDropIn({ clientToken, amount, orderId, onSuccess, onError, }: BraintreeDropInProps) { const dropinInstance = useRef<Dropin | null>(null); const [isReady, setIsReady] = useState(false); const [isProcessing, setIsProcessing] = useState(false); useEffect(() => { const initializeDropin = async () => { try { const instance = await dropin.create({ authorization: clientToken, container: '#dropin-container', paypal: { flow: 'checkout', amount: amount.toFixed(2), currency: 'USD', }, venmo: { allowNewBrowserTab: false, }, card: { cardholderName: { required: true, }, }, dataCollector: true, }); dropinInstance.current = instance; setIsReady(true); } catch (error) { console.error('Drop-in initialization failed:', error); onError(error); } }; initializeDropin(); return () => { dropinInstance.current?.teardown(); }; }, [clientToken, amount]); const handlePayment = useCallback(async () => { if (!dropinInstance.current || isProcessing) return; setIsProcessing(true); try { const { nonce, deviceData } = await dropinInstance.current.requestPaymentMethod(); const response = await fetch('/api/braintree/create-transaction', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentMethodNonce: nonce, amount, orderId, deviceData, }), }); const result = await response.json(); if (result.success) { onSuccess(result); } else { onError(new Error(result.errorMessage)); } } catch (error) { onError(error); } finally { setIsProcessing(false); } }, [amount, orderId, onSuccess, onError, isProcessing]); return ( <div className="space-y-4"> <div id="dropin-container" /> <button onClick={handlePayment} disabled={!isReady || isProcessing} className="w-full py-3 px-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 disabled:bg-gray-400 transition-colors" > {isProcessing ? 'Processing...' : `Pay $${amount.toFixed(2)}`} </button> </div> ); }

Vault for Recurring Payments

// Create customer with vaulted payment method async createCustomerWithPaymentMethod(params: { email: string; paymentMethodNonce: string; }): Promise<{ customerId: string; paymentMethodToken: string }> { const result = await gateway.customer.create({ email: params.email, paymentMethodNonce: params.paymentMethodNonce, }); if (!result.success) { throw new Error(result.message); } return { customerId: result.customer.id, paymentMethodToken: result.customer.paymentMethods[0].token, }; } // Charge saved payment method async chargeVaultedPaymentMethod(params: { paymentMethodToken: string; amount: number; orderId: string; }): Promise<{ success: boolean; transactionId?: string }> { const result = await gateway.transaction.sale({ amount: params.amount.toFixed(2), paymentMethodToken: params.paymentMethodToken, orderId: params.orderId, options: { submitForSettlement: true, }, }); return { success: result.success, transactionId: result.transaction?.id, }; }

Webhook Configuration

// services/braintree/WebhookService.ts import { gateway } from '@/config/braintree'; export class BraintreeWebhookService { async handleWebhook(signature: string, payload: string): Promise<void> { const notification = await gateway.webhookNotification.parse( signature, payload ); switch (notification.kind) { case 'transaction_settled': await this.handleTransactionSettled(notification); break; case 'transaction_settlement_declined': await this.handleSettlementDeclined(notification); break; case 'dispute_opened': await this.handleDisputeOpened(notification); break; case 'subscription_charged_successfully': await this.handleSubscriptionCharged(notification); break; case 'subscription_charged_unsuccessfully': await this.handleSubscriptionFailed(notification); break; } } }

Conclusion

Braintree excels for:

  1. PayPal integration - Native, seamless experience
  2. Marketplace payments - Split payments built-in
  3. Recurring billing - Vault + subscriptions
  4. US market - Venmo support

Choose Braintree when PayPal is critical for your customers.

Related Articles

Security Engineering18 min read

API Security Hardening: A Practitioner's Guide

Secure your APIs with rate limiting, input validation, and CORS configuration. Production-tested checklist covering authentication, encryption, and error handling.