Profiles·Public

braintree

semver>=3.0.0postconditions19functions14last verified2026-04-16coverage score79%

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]

Sources

Every postcondition cites at least one of these. Numbered to match the footnotes above.

  1. [1]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/general/exceptions/node
  2. [2]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/transaction/sale/node
  3. [3]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/client-token/generate/node
  4. [4]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/customer/create/node
  5. [5]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/transaction/refund/node
  6. [6]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/transaction/void/node
  7. [7]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/general/result-objects
  8. [8]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/transaction/submit-for-settlement/node
  9. [9]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/subscription/cancel/node
  10. [10]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/subscription/retry-charge/node
  11. [11]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/subscription/update/node
  12. [12]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/customer/delete/node
  13. [13]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/guides/webhooks/overview
  14. [14]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/dispute/accept/node
  15. [15]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/reference/request/dispute/finalize/node
  16. [16]developer.paypal.com/braintree/docshttps://developer.paypal.com/braintree/docs/guides/disputes/overview
Need a different package?
Request a profile