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_settlement → settled transition with simulated settlement in sandbox.
When to use
Authoring
Setup
Get sandbox credentials at braintreepayments.com/sandbox - merchant ID + public + private keys.
Install
npm install braintree
pip install braintreeInitialize
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:
| Card | Behaviour |
|---|---|
| 4111 1111 1111 1111 | Authorized + settled |
| 5555 5555 5555 4444 | Authorized + settled (Mastercard) |
| 4000 1111 1111 1115 | Processor declined (general) |
| 4000 0000 0000 0002 | Processor declined |
| 4000 0000 0000 1109 | 3DS frictionless |
| 4000 0000 0000 1091 | 3DS challenge |
By amount:
| Amount | Behaviour |
|---|---|
| $2000.00 | Processor declined (insufficient funds) |
| $2999.00 | Fraud failure |
| $3000.00 | Bank 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 testCI 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-pattern | Why it fails | Fix |
|---|---|---|
| Use prod credentials in tests | Real charges | Sandbox-only |
Skip submitForSettlement: true | Transaction stays in authorized state | Set explicitly |
| Test refund without settlement | Refund requires settled state | Settle first via testing API |
| Hardcoded amounts ignoring magic values | Trip amount-based behaviours unexpectedly | Document amount-vs-behaviour |
Use fake-valid-nonce in production code path | Sandbox-only | Real nonces from Drop-in |
| Skip webhook signature validation | Spoof risk | gateway.webhookNotification.parse validates |
| Long-polling settled-state in tests | Slow | gateway.testing.settle synchronously |
| Test only success path | Decline / fraud / bank-failure paths matter | Test amount-based magic values |