braintree
semver
>=3.0.0postconditions19functions14last verified2026-04-16coverage score79%Postconditions — what we check
- sale · api-errorerrorWhenInfrastructure failure: invalid API credentials (AuthenticationError), network unreachable (ServiceUnavailableError), request timeout (GatewayTimeoutError), Braintree server error (ServerError), or rate limit (TooManyRequestsError). Distinct from business logic failures (result.success=false) which are returned in the result object and do not throw.Throws
AuthenticationError (401 — wrong API credentials), AuthorizationError (403 — key lacks permission), ServerError (5xx — Braintree internal error), GatewayTimeoutError (Braintree gateway timeout), ServiceUnavailableError (Braintree down), TooManyRequestsError (rate limit). All are subclasses of BraintreeError.Required handlingCaller MUST wrap in try-catch. Payment processing errors must not propagate unhandled — an uncaught exception means the user's payment status is unknown. Minimum handling: try { const result = await gateway.transaction.sale({ amount, paymentMethodNonce }); if (result.success) { // Payment processed: result.transaction.id } else { // Business logic failure: result.message, result.errors.deepErrors() } } catch (err) { // Infrastructure failure: log and surface appropriate error to user } ALSO check result.success for business logic failures (declined, invalid card, validation errors) — these are NOT thrown but returned in result.success=false.costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - generate · api-errorerrorWhenInfrastructure failure: invalid API credentials, network unreachable, server error, or timeout. Without a valid client token, the client-side Braintree SDK cannot initialize and the user cannot complete checkout.Throws
AuthenticationError, AuthorizationError, ServerError, GatewayTimeoutError, ServiceUnavailableError, TooManyRequestsError.Required handlingCaller MUST wrap in try-catch. A failed clientToken.generate() means the checkout page cannot load Braintree's Drop-in UI or Hosted Fields. Return a 5xx to the client; do not expose raw error details. try { const response = await gateway.clientToken.generate({}); return response.clientToken; } catch (err) { // Log and return 500 to client }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisibleSources[3] - create · api-errorerrorWhenInfrastructure failure: invalid API credentials, network unreachable, server error, timeout, or rate limit. Separate from validation failures returned in result object.Throws
AuthenticationError, AuthorizationError, ServerError, GatewayTimeoutError, ServiceUnavailableError, TooManyRequestsError.Required handlingCaller MUST wrap in try-catch. Check result.success after for validation failures. try { const result = await gateway.customer.create({ ... }); if (result.success) { // result.customer.id } else { // result.message, result.errors.deepErrors() } } catch (err) { // Infrastructure failure }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisibleSources[4] - find · api-errorerrorWhenInfrastructure or lookup failure: NotFoundError (invalid/unknown ID), AuthenticationError, ServerError, GatewayTimeoutError, or network failure. NotFoundError is extremely common in production (stale IDs, expired records).Throws
NotFoundError (ID not found — very common in production), AuthenticationError, AuthorizationError, ServerError, GatewayTimeoutError, ServiceUnavailableError.Required handlingCaller MUST wrap in try-catch, specifically handling NotFoundError. try { const transaction = await gateway.transaction.find(transactionId); } catch (err) { if (err instanceof braintree.errorTypes.notFoundError) { // Transaction not found — return 404 } throw err; }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisibleSources[1] - refund · api-errorerrorWhenInfrastructure failure or invalid transaction state: transaction not yet settled, transaction already fully refunded, or network/server error.Throws
AuthenticationError, AuthorizationError, NotFoundError, ServerError, GatewayTimeoutError, ServiceUnavailableError.Required handlingCaller MUST wrap in try-catch. Failed refunds need explicit handling — customer must be notified if refund cannot be processed. try { const result = await gateway.transaction.refund(transactionId, amount); if (result.success) { // Refund processed: result.transaction.id } } catch (err) { // Log and notify: refund failed }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisibleSources[5] - void · void-no-try-catcherrorWhenawait gateway.transaction.void(transactionId) is called without a surrounding try-catch block.Throws
AuthenticationError (wrong API credentials), AuthorizationError (key lacks permission), NotFoundError (transaction ID does not exist), ServerError (Braintree internal error), GatewayTimeoutError (Braintree gateway timeout), ServiceUnavailableError (Braintree down). All are subclasses of BraintreeError (err.type identifies the subclass).Required handlingCaller MUST wrap in try-catch. A voiding failure means the transaction may still result in a customer charge. The result object also carries validation errors (result.success=false) when the transaction state is invalid for voiding. try { const result = await gateway.transaction.void(transactionId); if (!result.success) { // Invalid state for void: result.message, result.errors.deepErrors() } } catch (err) { // Infrastructure failure: log and notify operations team }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - void · void-wrong-stateerrorWhengateway.transaction.void() is called on a transaction that has already been settled, is in "settlement_declined", "voided", or "failed" state. The transaction is not in a voidable state ("authorized", "submitted_for_settlement", "settlement_pending").Throws
Does NOT throw — returns result object with result.success=false and validation errors. Common error: "91504 Transaction cannot be voided" (transaction already settled).Required handlingCaller MUST check result.success after void. Settlement windows close quickly — transactions auto-settle within 1 business day. Void attempts on settled transactions must fall back to refund. Not checking result.success causes silent void failures while the customer is still charged.costhighin prodsilent failureusers seelost transactionvisibilitysilent - submitForSettlement · submit-settlement-no-try-catcherrorWhenawait gateway.transaction.submitForSettlement(transactionId) is called without a surrounding try-catch block.Throws
AuthenticationError, AuthorizationError, NotFoundError (invalid transaction ID), ServerError, GatewayTimeoutError, ServiceUnavailableError, TooManyRequestsError. All are subclasses of BraintreeError.Required handlingCaller MUST wrap in try-catch. Failure to submit for settlement means the authorization expires and no funds are collected. This is particularly critical for "authorize only" flows where submitForSettlement is called separately. try { const result = await gateway.transaction.submitForSettlement(transactionId); if (!result.success) { // result.errors.deepErrors() — e.g. transaction already submitted or settled } } catch (err) { // Infrastructure failure — retry with backoff }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - submitForSettlement · submit-settlement-wrong-stateerrorWhensubmitForSettlement() is called on a transaction not in "authorized" state — already submitted, already settled, voided, or failed.Throws
Does NOT throw — returns result.success=false with validation errors. Common error: "91507 Cannot submit for settlement unless status is authorized."Required handlingCaller MUST check result.success. Silent failure to capture payment means the merchant provides goods/services but never collects payment.costhighin prodsilent failureusers seelost transactionvisibilitysilentSources[8] - cancel · subscription-cancel-no-try-catcherrorWhenawait gateway.subscription.cancel(subscriptionId) is called without a surrounding try-catch block.Throws
NotFoundError (subscription ID does not exist or belongs to different merchant), AuthenticationError, AuthorizationError, ServerError, GatewayTimeoutError, ServiceUnavailableError.Required handlingCaller MUST wrap in try-catch. An uncaught NotFoundError on cancel means the subscription remains active and continues billing the customer. This is especially critical during account deletion or "cancel subscription" user flows. try { await gateway.subscription.cancel(subscriptionId); // Subscription successfully canceled — update local DB record } catch (err) { if (err.type === 'notFoundError') { // Subscription already gone or wrong ID — treat as canceled } throw err; }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - retryCharge · retry-charge-no-try-catcherrorWhenawait gateway.subscription.retryCharge(subscriptionId) is called without a surrounding try-catch block.Throws
AuthenticationError, AuthorizationError, NotFoundError (invalid subscription), ServerError, GatewayTimeoutError, ServiceUnavailableError, TooManyRequestsError. Internally delegates to transaction.sale() — same exception hierarchy.Required handlingCaller MUST wrap in try-catch. Also MUST check result.success — declined cards return result.success=false with result.transaction.processorResponseCode/Text. Retry failures in dunning flows must be logged and tracked to avoid infinite retry loops. try { const result = await gateway.subscription.retryCharge(subscriptionId, undefined, true); if (result.success) { // Recovered: result.transaction.id } else { // Card declined: result.message, result.transaction.processorResponseText } } catch (err) { // Infrastructure failure }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - retryCharge · retry-charge-result-not-checkederrorWhenresult.success is not checked after retryCharge(). A declined card returns result.success=false with no exception thrown — the caller proceeds as if the retry succeeded.Throws
Does NOT throw on card decline — returns result.success=falseRequired handlingCaller MUST check result.success after retryCharge(). Unchecked result means the dunning flow records a "success" while the subscription remains past-due and the customer continues to receive service without paying.costhighin prodsilent failureusers seelost transactionvisibilitysilentSources[7] - update · update-no-try-catcherrorWhenawait gateway.subscription.update(), gateway.customer.update(), or gateway.paymentMethod.update() is called without a surrounding try-catch block.Throws
AuthenticationError, AuthorizationError, NotFoundError (invalid ID or token), ServerError, GatewayTimeoutError, ServiceUnavailableError, TooManyRequestsError.Required handlingCaller MUST wrap in try-catch. Also check result.success — validation failures (invalid plan, billing amount below minimum) are returned in the result object, not thrown. try { const result = await gateway.subscription.update(subscriptionId, { planId: newPlan }); if (!result.success) { // result.errors.deepErrors() — e.g. plan not found, invalid amount } } catch (err) { // Infrastructure failure }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - delete · delete-no-try-catcherrorWhenawait gateway.customer.delete(customerId) or gateway.paymentMethod.delete(token) is called without a surrounding try-catch block.Throws
NotFoundError (customer or token does not exist — extremely common after account merges or external deletions), AuthorizationError (insufficient API key permissions for vault deletion), AuthenticationError, ServerError, GatewayTimeoutError.Required handlingCaller MUST wrap in try-catch. NotFoundError on delete should usually be treated as idempotent success (the resource is gone regardless). Unhandled NotFoundError in GDPR deletion flows breaks the data-removal guarantee. try { await gateway.customer.delete(customerId); } catch (err) { if (err.type === 'notFoundError') { // Already deleted — treat as success for idempotency return; } throw err; // Re-throw infrastructure failures }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - parse · webhook-parse-no-try-catcherrorWhenawait gateway.webhookNotification.parse(signature, payload) is called without a surrounding try-catch block.Throws
InvalidSignatureError in three cases confirmed from source: 1. signature parameter is missing or falsy 2. payload parameter is missing or falsy 3. payload contains illegal non-base64 characters 4. signature does not match payload (HMAC mismatch — tampered webhook or wrong key) Also: ServerError or UnexpectedError if XML parsing fails on malformed payload.Required handlingCaller MUST wrap in try-catch. InvalidSignatureError means the webhook was either tampered with or the wrong API keys are in use. Do NOT process the notification. Always return HTTP 200 to Braintree regardless to prevent re-delivery of bad webhooks. try { const notification = await gateway.webhookNotification.parse( req.body.bt_signature, req.body.bt_payload ); // Process notification.kind (e.g. 'subscription_charged_successfully') } catch (err) { if (err.type === 'invalidSignatureError') { // Log attempted tampering — do NOT process return res.sendStatus(200); // Prevent re-delivery } throw err; }costmediumin prodimmediate exceptionusers seelost transactionvisibilitysilentSources[13] - parse · webhook-signature-not-validatederrorWhenWebhook handler processes notification.kind without first validating that parse() succeeded — or catches the error and processes anyway.Throws
Does not throw — security vulnerability: processing tampered webhook dataRequired handlingThe InvalidSignatureError MUST be treated as a hard stop. Processing a webhook that fails signature validation can trigger fraudulent subscription state changes, refund triggers, or account actions based on attacker-controlled data.costhighin prodsilent failureusers seelost transactionvisibilitysilentSources[13] - accept · dispute-accept-no-try-catcherrorWhenawait gateway.dispute.accept(disputeId) is called without a surrounding try-catch block.Throws
NotFoundError (dispute ID does not exist or already closed), AuthenticationError, AuthorizationError (insufficient dispute management permissions), ServerError, GatewayTimeoutError, ServiceUnavailableError.Required handlingCaller MUST wrap in try-catch. Also check result.success — attempting to accept a dispute that is not in OPEN status returns result.success=false. An unhandled exception in a chargeback workflow means the dispute response deadline may be missed, resulting in an automatic loss. try { const result = await gateway.dispute.accept(disputeId); if (!result.success) { // Dispute not in OPEN state or other validation error } } catch (err) { // Infrastructure failure — alert operations team }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - finalize · dispute-finalize-no-try-catcherrorWhenawait gateway.dispute.finalize(disputeId) is called without a surrounding try-catch block.Throws
NotFoundError (dispute ID invalid or already finalized), AuthorizationError (insufficient dispute management API permissions), AuthenticationError, ServerError, GatewayTimeoutError, ServiceUnavailableError.Required handlingCaller MUST wrap in try-catch. A finalize failure means evidence was added but never submitted to the card network — the merchant loses the dispute by default even though evidence exists. try { const result = await gateway.dispute.finalize(disputeId); if (!result.success) { // result.errors — e.g. no evidence added before finalization } } catch (err) { // Alert operations team: dispute may be lost }costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible - finalize · finalize-not-called-after-evidenceerrorWhenEvidence is added via addTextEvidence() or addFileEvidence() but finalize() is never called (e.g. error handling skips finalize, or developer forgot the step).Throws
Does not throw — silent failure: evidence uploaded but never submittedRequired handlingFinalize MUST always be called after evidence is added. It is a separate required step — adding evidence without finalizing submits nothing to the card network. The dispute deadline passes and the merchant loses even with valid evidence.costhighin prodsilent failureusers seelost transactionvisibilitysilentSources[16]
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/general/exceptions/node
- [2]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/transaction/sale/node
- [3]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/client-token/generate/node
- [4]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/customer/create/node
- [5]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/transaction/refund/node
- [6]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/transaction/void/node
- [7]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/general/result-objects
- [8]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/transaction/submit-for-settlement/node
- [9]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/subscription/cancel/node
- [10]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/subscription/retry-charge/node
- [11]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/subscription/update/node
- [12]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/customer/delete/node
- [13]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/guides/webhooks/overview
- [14]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/dispute/accept/node
- [15]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/dispute/finalize/node
- [16]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/guides/disputes/overview
Need a different package?
Request a profile