From qa-payment
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-payment:braintree-test-cardsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [developer.paypal.com/braintree/docs/reference/general/testing](https://developer.paypal.com/braintree/docs/reference/general/testing),
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 braintree
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!,
});
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:
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 test
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-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.npx claudepluginhub testland/qa --plugin qa-paymentProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Searches MemPalace before answering questions about past work, people, projects, or prior decisions. Returns verbatim stored content instead of guessing from model memory.