Testland
Browse all skills & agents

braintree-test-cards

Wraps Braintree (PayPal-owned) sandbox testing patterns: sandbox merchant credentials, Drop-in / Hosted Fields client-side patterns, the Transaction lifecycle (submitted_for_settlement → settled), Braintree's distinctive test-card behaviours (specific PANs trigger specific errors), and the webhook verification (Braintree Webhook Parser). Use when testing Braintree-integrated code. Composes payment-flow-states-reference + 3ds-test-flow-reference.

braintree-test-cards

Overview

Per developer.paypal.com/braintree/docs/reference/general/testing, the Braintree sandbox accepts the same API as production with deterministic test-card responses.

The notable distinction: Braintree's Transaction state machine includes an explicit submitted_for_settlementsettled transition with simulated settlement in sandbox.

When to use

  • Tests for code using Braintree.
  • Drop-in / Hosted Fields client-side flow tests.
  • 3DS tests per 3ds-test-flow-reference.

Authoring

Setup

Get sandbox credentials at braintreepayments.com/sandbox - merchant ID + public + private keys.

Install

npm install braintree
pip install braintree

Initialize

import braintree from 'braintree';

const gateway = new braintree.BraintreeGateway({
  environment: braintree.Environment.Sandbox,
  merchantId: process.env.BT_SANDBOX_MERCHANT_ID!,
  publicKey: process.env.BT_SANDBOX_PUBLIC_KEY!,
  privateKey: process.env.BT_SANDBOX_PRIVATE_KEY!,
});

Test cards

Per developer.paypal.com/braintree/docs/reference/general/testing/node:

CardBehaviour
4111 1111 1111 1111Authorized + settled
5555 5555 5555 4444Authorized + settled (Mastercard)
4000 1111 1111 1115Processor declined (general)
4000 0000 0000 0002Processor declined
4000 0000 0000 11093DS frictionless
4000 0000 0000 10913DS challenge

By amount:

AmountBehaviour
$2000.00Processor declined (insufficient funds)
$2999.00Fraud failure
$3000.00Bank failure

This amount-based behaviour is unique to Braintree.

Transaction

const result = await gateway.transaction.sale({
  amount: '10.00',
  paymentMethodNonce: 'fake-valid-nonce',  // From Braintree client SDK
  options: { submitForSettlement: true },
});

expect(result.success).toBe(true);
expect(result.transaction.status).toBe('submitted_for_settlement');

fake-valid-nonce is a sandbox-only nonce that represents a successful tokenization. Real flow uses Drop-in or Hosted Fields to produce a real nonce.

Settle in sandbox

Sandbox transactions don't auto-settle; you can force settlement via the testing API:

await gateway.testing.settle(transactionId);
const result = await gateway.transaction.find(transactionId);
expect(result.status).toBe('settled');

Per developer.paypal.com/braintree/docs/reference/general/testing/node#settle-transaction: the testing methods are sandbox-only.

Refund

const refundResult = await gateway.transaction.refund(transactionId);
expect(refundResult.transaction.type).toBe('credit');

Refunds can only happen after settlement; submit-for-settlement then settle (testing) then refund.

Webhook handling

Per developer.paypal.com/braintree/docs/guides/webhooks:

const webhookNotification = await gateway.webhookNotification.parse(
  request.body.bt_signature,
  request.body.bt_payload,
);

expect(webhookNotification.kind).toBeDefined();
// e.g., 'transaction_settled', 'transaction_settlement_declined'

The parser validates the signature; an invalid one throws.

Drop-in / Hosted Fields flow

Client-side (browser):

braintree.dropin.create({
  authorization: clientToken,
  selector: '#dropin-container',
}, (err, instance) => {
  // ...
  instance.requestPaymentMethod((err, payload) => {
    // payload.nonce — send to server
    fetch('/api/checkout', { method: 'POST', body: JSON.stringify({ nonce: payload.nonce }) });
  });
});

Tests for this layer need Playwright + Drop-in's test mode.

Running

npm test

CI integration

jobs:
  braintree-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
      - run: npm ci && npm test
        env:
          BT_SANDBOX_MERCHANT_ID: ${{ secrets.BT_SANDBOX_MERCHANT_ID }}
          BT_SANDBOX_PUBLIC_KEY: ${{ secrets.BT_SANDBOX_PUBLIC_KEY }}
          BT_SANDBOX_PRIVATE_KEY: ${{ secrets.BT_SANDBOX_PRIVATE_KEY }}

Anti-patterns

Anti-patternWhy it failsFix
Use prod credentials in testsReal chargesSandbox-only
Skip submitForSettlement: trueTransaction stays in authorized stateSet explicitly
Test refund without settlementRefund requires settled stateSettle first via testing API
Hardcoded amounts ignoring magic valuesTrip amount-based behaviours unexpectedlyDocument amount-vs-behaviour
Use fake-valid-nonce in production code pathSandbox-onlyReal nonces from Drop-in
Skip webhook signature validationSpoof riskgateway.webhookNotification.parse validates
Long-polling settled-state in testsSlowgateway.testing.settle synchronously
Test only success pathDecline / fraud / bank-failure paths matterTest amount-based magic values

Limitations

  • Amount-based test behaviour is sandbox-specific. Production doesn't use these magic values.
  • Settlement is real-time in test via testing API. Prod settlement is overnight batch.
  • Drop-in / Hosted Fields require browser context. Server unit tests use fake nonces; full flow needs Playwright.
  • gateway.testing.* methods don't exist in production. Be deliberate about test-only code paths.

References