Profiles·Public

braintree

semver>=3.0.0postconditions22functions16last verified2026-06-24coverage score82%

Postconditions: what we check

  • sale · api-error
    error
    WhenInfrastructure 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.
    ThrowsAuthenticationError (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
    Sources[1][2]
  • generate · api-error
    error
    WhenInfrastructure 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.
    ThrowsAuthenticationError, 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 transactionvisibilityvisible
    Sources[3]
  • create · api-error
    error
    WhenInfrastructure failure: invalid API credentials, network unreachable, server error, timeout, or rate limit. Separate from validation failures returned in result object.
    ThrowsAuthenticationError, 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 transactionvisibilityvisible
    Sources[4]
  • find · api-error
    error
    WhenInfrastructure or lookup failure: NotFoundError (invalid/unknown ID), AuthenticationError, ServerError, GatewayTimeoutError, or network failure. NotFoundError is extremely common in production (stale IDs, expired records).
    ThrowsNotFoundError (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 transactionvisibilityvisible
    Sources[1]
  • refund · api-error
    error
    WhenInfrastructure failure or invalid transaction state: transaction not yet settled, transaction already fully refunded, or network/server error.
    ThrowsAuthenticationError, 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 transactionvisibilityvisible
    Sources[5]
  • void · void-no-try-catch
    error
    Whenawait gateway.transaction.void(transactionId) is called without a surrounding try-catch block.
    ThrowsAuthenticationError (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
    Sources[1][6]
  • void · void-wrong-state
    error
    Whengateway.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").
    ThrowsDoes 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
    Sources[6][7]
  • submitForSettlement · submit-settlement-no-try-catch
    error
    Whenawait gateway.transaction.submitForSettlement(transactionId) is called without a surrounding try-catch block.
    ThrowsAuthenticationError, 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
    Sources[8][1]
  • submitForSettlement · submit-settlement-wrong-state
    error
    WhensubmitForSettlement() is called on a transaction not in "authorized" state — already submitted, already settled, voided, or failed.
    ThrowsDoes 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 transactionvisibilitysilent
    Sources[8]
  • cancel · subscription-cancel-no-try-catch
    error
    Whenawait gateway.subscription.cancel(subscriptionId) is called without a surrounding try-catch block.
    ThrowsNotFoundError (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
    Sources[9][1]
  • retryCharge · retry-charge-no-try-catch
    error
    Whenawait gateway.subscription.retryCharge(subscriptionId) is called without a surrounding try-catch block.
    ThrowsAuthenticationError, 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
    Sources[10][1]
  • retryCharge · retry-charge-result-not-checked
    error
    Whenresult.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.
    ThrowsDoes NOT throw on card decline — returns result.success=false
    Required 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 transactionvisibilitysilent
    Sources[7]
  • update · update-no-try-catch
    error
    Whenawait gateway.subscription.update(), gateway.customer.update(), or gateway.paymentMethod.update() is called without a surrounding try-catch block.
    ThrowsAuthenticationError, 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
    Sources[11][1]
  • delete · delete-no-try-catch
    error
    Whenawait gateway.customer.delete(customerId) or gateway.paymentMethod.delete(token) is called without a surrounding try-catch block.
    ThrowsNotFoundError (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
    Sources[12][1]
  • parse · webhook-parse-no-try-catch
    error
    Whenawait gateway.webhookNotification.parse(signature, payload) is called without a surrounding try-catch block.
    ThrowsInvalidSignatureError 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 transactionvisibilitysilent
    Sources[13]
  • parse · webhook-signature-not-validated
    error
    WhenWebhook handler processes notification.kind without first validating that parse() succeeded — or catches the error and processes anyway.
    ThrowsDoes not throw — security vulnerability: processing tampered webhook data
    Required 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 transactionvisibilitysilent
    Sources[13]
  • accept · dispute-accept-no-try-catch
    error
    Whenawait gateway.dispute.accept(disputeId) is called without a surrounding try-catch block.
    ThrowsNotFoundError (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
    Sources[14][1]
  • finalize · dispute-finalize-no-try-catch
    error
    Whenawait gateway.dispute.finalize(disputeId) is called without a surrounding try-catch block.
    ThrowsNotFoundError (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
    Sources[15][1]
  • finalize · finalize-not-called-after-evidence
    error
    WhenEvidence is added via addTextEvidence() or addFileEvidence() but finalize() is never called (e.g. error handling skips finalize, or developer forgot the step).
    ThrowsDoes not throw — silent failure: evidence uploaded but never submitted
    Required 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 transactionvisibilitysilent
    Sources[16]
  • addTextEvidence · dispute-add-text-evidence-no-try-catch
    error
    Whenawait gateway.dispute.addTextEvidence(disputeId, content) is called without a surrounding try-catch block.
    ThrowsNotFoundError (dispute ID invalid, closed, or already finalized — addEvidence only valid in OPEN state), InvalidKeysError (content is null/empty, sequenceNumber is not a numeric string, category is null / non-string / empty), AuthenticationError, AuthorizationError (missing dispute management permissions), ServerError, GatewayTimeoutError, ServiceUnavailableError.
    Required handlingCaller MUST wrap in try-catch. Also check result.success — Braintree returns result.success=false for soft validation failures (e.g. dispute already finalized server-side, evidence type rejected by the network). An uncaught NotFoundError or InvalidKeysError aborts the evidence collection workflow mid-flow, leaving a partially-submitted dispute that will then lose by default at finalize time. try { const result = await gateway.dispute.addTextEvidence( disputeId, { content: 'Delivery confirmation: tracking 1Z999...', category: 'PROOF_OF_FULFILLMENT' } ); if (!result.success) { // result.errors — evidence rejected (wrong category, dispute already closed) // Decide whether to retry with a different category or abort } } catch (err) { if (err.type === 'notFoundError') { // Dispute may have transitioned out of OPEN — re-fetch state return; } if (err.name === 'InvalidKeysError') { // Bad input — log and surface validation error to operator throw new Error(`Evidence validation failed: ${err.message}`); } // Infrastructure failure — alert ops, dispute deadline at risk throw err; }
    costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible
  • addTextEvidence · add-text-evidence-result-not-checked
    error
    WhenCaller does not inspect result.success on the response from gateway.dispute.addTextEvidence(). A result.success=false response means the evidence was REJECTED by Braintree (wrong category, invalid format, dispute state mismatch) but no exception is thrown — silently dropping the evidence.
    ThrowsDoes not throw — silent failure: evidence rejected but caller assumes success
    Required handlingThe dual error model (thrown exception + result.success) applies to every addEvidence call. result.success=false with non-empty result.errors must be treated as a hard failure: the evidence did NOT make it onto the dispute and finalize() will submit an empty record. Always inspect result.success and log result.errors.deepErrors() before proceeding to finalize.
    costhighin prodsilent failureusers seelost transactionvisibilitysilent
    Sources[17]
  • addFileEvidence · dispute-add-file-evidence-no-try-catch
    error
    Whenawait gateway.dispute.addFileEvidence(disputeId, documentId) is called without a surrounding try-catch block.
    ThrowsNotFoundError (dispute ID invalid / closed, OR documentId is null / empty / whitespace — source throws "document with id 'null' not found"), AuthenticationError, AuthorizationError (missing dispute management permissions), ServerError, GatewayTimeoutError, ServiceUnavailableError.
    Required handlingCaller MUST wrap in try-catch. Most-common failure mode in production: a stale documentId reference from a documentUpload that was never fully persisted, OR the dispute transitioned out of OPEN status between fetching it and adding evidence. Both surface as NotFoundError and must be distinguished from infrastructure errors before deciding whether to retry. try { const result = await gateway.dispute.addFileEvidence( disputeId, { documentId: uploadId, category: 'PROOF_OF_FULFILLMENT' } ); if (!result.success) { // File rejected (wrong category for dispute reason, file too large server-side) throw new Error(`File evidence rejected: ${result.message}`); } } catch (err) { if (err.type === 'notFoundError') { // Re-verify dispute state and documentUpload existence before retry return; } // Infrastructure failure — alert ops, evidence not attached throw err; }
    costhighin prodimmediate exceptionusers seelost transactionvisibilityvisible

Sources

Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.

Official documentation
  • [1]
    developer.paypal.com/braintree/docs/reference
    Node
  • [2]
    developer.paypal.com/braintree/docs/reference
    Node
  • [3]
    developer.paypal.com/braintree/docs/reference
    Node
  • [4]
    developer.paypal.com/braintree/docs/reference
    Node
  • [5]
    developer.paypal.com/braintree/docs/reference
    Node
  • [6]
    developer.paypal.com/braintree/docs/reference
    Node
  • [7]
    developer.paypal.com/braintree/docs/reference
    Result Objects
  • [8]
    developer.paypal.com/braintree/docs/reference
    Node
  • [9]
    developer.paypal.com/braintree/docs/reference
    Node
  • [10]
    developer.paypal.com/braintree/docs/reference
    Node
  • [11]
    developer.paypal.com/braintree/docs/reference
    Node
  • [12]
    developer.paypal.com/braintree/docs/reference
    Node
  • [13]
    developer.paypal.com/braintree/docs/guides
    Overview
  • [14]
    developer.paypal.com/braintree/docs/reference
    Node
  • [15]
    developer.paypal.com/braintree/docs/reference
    Node
  • [16]
    developer.paypal.com/braintree/docs/guides
    Overview
  • [17]
    developer.paypal.com/braintree/docs/reference
    Node
  • [18]
    developer.paypal.com/braintree/docs/reference
    Node

Research notes

Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.

Sources: braintree

Official Documentation

SDK Repository

Real-World Usage

  • nestjsx/nestjs-braintree (76 stars): TypeScript NestJS module. Exposes gateway methods directly with no try-catch — all exceptions propagate to callers. Confirmed true positive pattern. Methods: transaction.sale, transaction.refund, subscription.create, subscription.cancel.

Package

Error Handling Contract Rationale

Braintree has a dual error model (unusual among payment APIs):

  1. Infrastructure errors → thrown exceptions (requires try-catch)
  2. Business logic errors → result objects (requires result.success check)

This contract covers path 1 (thrown exceptions). Path 2 (result.success) requires separate dataflow analysis not yet supported by the V2 analyzer.

Evidence quality: partial — nestjsx/nestjs-braintree confirms the antipattern exists in production TypeScript code, but the repo has <100 stars. More real-world evidence needed.

Need a different package?
Request a profile