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.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Medium
Suggest reviewing before use
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.
3ds-test-flow-reference.Get sandbox credentials at braintreepayments.com/sandbox - merchant ID + public + private keys.
npm install braintree
pip install braintreeimport 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!,
});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.
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.
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.
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.
Per developer.paypal.com/braintree/docs/guides/webhooks/parse/node:
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.
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.
npm testjobs:
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-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 |
gateway.testing.* methods don't exist in production. Be
deliberate about test-only code paths.payment-flow-states-reference,
3ds-test-flow-reference,
pci-dss-scope-reference.stripe-test-cards-and-webhooks,
adyen-test-mode,
paypal-sandbox.