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.
Table of Contents
- Why Braintree
- Server-Side Setup
- Drop-in UI Integration
- Hosted Fields for Custom UI
- PayPal and Venmo Integration
- Vault for Recurring Payments
- Webhook Configuration
- 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:
- PayPal integration - Native, seamless experience
- Marketplace payments - Split payments built-in
- Recurring billing - Vault + subscriptions
- US market - Venmo support
Choose Braintree when PayPal is critical for your customers.
Related Articles
Payment Integrations24 min read
PayPal Payment Integration: Production Implementation Guide
Integrate PayPal with Orders API and Smart Buttons. Covers webhook setup, subscription payments, and real error handling patterns from production.
Payment Integrations28 min read
Stripe Payment Integration: Production Patterns for React and Node.js
Production Stripe integration with Payment Intents, webhooks, and 3D Secure. Covers subscription billing, error handling, and PCI compliance patterns.
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.
Security Engineering21 min read
Authentication and Authorization in Production Systems
Implement secure JWT authentication with refresh token rotation, RBAC, and OAuth 2.0 flows. Production patterns from healthcare and government systems.