# Testing payments: cards, scenarios, and what sandboxes miss

URL: https://www.paydude.io/resources/testing-payments
Type: Guide
Published: 2026-09-28 · Updated: 2026-09-28
Summary: Test cards and the exact amounts that trigger each decline and dispute, a scenario matrix for your integration, and the failures you have to simulate yourself.

## Summary

In the Paydude sandbox, card failures are triggered by the payment amount, not the card number: $7,777.01 to $7,777.16 each produce a specific decline code, and every other amount succeeds. A good test suite covers the paths production will take — declines, retries, bank payments that settle overnight, subscriptions that go past due, duplicate webhooks — not just the successful charge. Some documented simulations did not work on the sandbox merchant we tested: address-verification prefixes and the failing CVC values both came back as passes. The failure that matters most — a request that succeeds but whose response never arrives — cannot be produced by any sandbox, so you have to inject it yourself.

A sandbox is very good at telling you your integration works. It is much worse at telling you your integration survives, because the happy path is the only one it produces unless you ask. This is the test plan we would want before taking real money: what the sandbox can simulate, what it claims to and did not, and what you have to fake yourself.

Everything marked as tested was run against the Paydude sandbox on 28 September 2026.

## Test cards

**Sandbox test cards, from the Paydude testing guide**

| Brand | Number | CVC | Expiry |
| --- | --- | --- | --- |
| Visa | 4111 1111 1111 1111 | Any 3 digits | Any future date |
| Visa | 4900 7700 0000 0001 | Any 3 digits | Any future date |
| Mastercard | 5100 4000 0000 0000 | Any 3 digits | Any future date |
| Discover (US) | 6011 2345 6789 0123 | Any 3 digits | Any future date |
| American Express (US) | 3701 234567 89017 | Any 4 digits | Any future date |

Collect them through the hosted card fields exactly as in production. Posting a card number to the API from your own server works in the sandbox, but it is the one habit you must not carry into production — see [PCI scope](https://www.paydude.io/resources/pci-scope-keeping-card-data-out-of-your-servers).

## Declines are triggered by amount

The card number does not decide the outcome. The payment intent's amount does:

**Decline trigger amounts (777701–777716 in cents)**

| Amount | Failure code |
| --- | --- |
| $7,777.01 | `insufficient_funds` ✓ tested |
| $7,777.02 | `avs_check_failed` |
| $7,777.03 | `generic_decline` |
| $7,777.04 | `call_issuer` |
| $7,777.05 | `expired_card` ✓ tested |
| $7,777.06 | `pickup_card` |
| $7,777.07 | `invalid_number` |
| $7,777.08 | `limit_exceeded` |
| $7,777.09 | `not_permitted` |
| $7,777.10 | `incorrect_cvc` |
| $7,777.11 | `service_not_allowed` |
| $7,777.12 | `invalid_expiry` |
| $7,777.13 | `card_not_supported` |
| $7,777.14 | `restricted_card` |
| $7,777.15 | `fraudulent` |
| $7,777.16 | `processing_error` |

> **Small amounts do not trigger declines:** Only the $7,777.xx series works. A payment intent for $77.01 or $77.05 simply succeeds — we tried both. If a test expecting a decline is passing a successful payment, check the amount first.

The trigger amounts suit a test harness that creates its own payment intents. For a test that goes through your checkout, you need a product priced at $7,777.01 in your test catalogue, or a test-only hook that overrides the amount.

## Disputes

Disputes are triggered the same way. These are from the testing guide and were not exercised for this article:

**Dispute trigger amounts, per the Paydude testing guide**

| Amount | Simulates | Ends in |
| --- | --- | --- |
| $7,777.97 | An inquiry | `warning_under_review` |
| $7,777.98 | An inquiry that escalates to a chargeback | `needs_response` |
| $7,777.99 | A formal chargeback | `needs_response` |

When you submit evidence, an `evidence_description` of `REVERSAL` resolves the dispute as won and `LOSS` as lost, according to the same guide.

## The scenarios worth a test each

**A test matrix for a payments integration**

| Scenario | How to produce it | What to assert |
| --- | --- | --- |
| Card approved | Any ordinary amount | Order paid only after `succeeded`, not after a 2xx |
| Card declined | $7,777.01 | 201 with `status: requires_payment_method`; a useful message; order still open |
| Retry after a decline | Decline, then confirm the same intent with a new card | Two charges on one intent; the order pays once |
| Reusing a declined card | Confirm a new intent with the declined payment method | Your UI asks for the card again rather than surfacing a 400 |
| Token expiry | Tokenise, wait past `expires_at`, confirm | A clear retry, not a stuck checkout |
| Bank payment | An ACH debit | Order stays pending through `processing`; fulfils on the webhook |
| Subscription payment fails | A subscription priced at $7,777.01 ✓ tested | Your dunning opens a case when it goes `past_due` |
| Duplicate webhook | Replay a stored event to your own endpoint | Nothing happens twice |
| Out-of-order webhook | Deliver a later event before an earlier one | Final state is correct |
| Refund | Refund a succeeded payment | Order marked refunded on success, not on request |
| Dispute | $7,777.99 | Someone is alerted before `closing_at` |

Two of those deserve a note from the sandbox runs. A declined payment method cannot be reused — confirming with it again returns 400, "The payment method … is not chargeable" — so the retry test has to tokenise a fresh card. And an unattached payment method carries an `expires_at`: in our runs it was **ten minutes** after creation. Read the field rather than hard-coding a window.

Subscriptions do not bill at the exact anchor time. A subscription anchored at 22:42 UTC was billed at 23:00:00 — the sandbox appears to run renewals on the hour — so a test waiting on a renewal should poll for up to an hour rather than sleep until the anchor.

Bank payments are slow on purpose. In the sandbox an ACH debit reported `processing` and succeeded about fourteen and a half hours later, so an end-to-end ACH test either waits overnight or stops at `processing` and tests the webhook handler separately with a recorded `payment_intent.succeeded` payload.

## What the sandbox did not simulate

The testing guide documents more simulations than the card-amount triggers. On the sandbox merchant we tested, these did not behave as described:

- **Address verification prefixes.** The guide says prefixing the street with a code such as `11` makes the postal-code check fail. We charged cards with street prefixes `10`, `11`, `12` and `A11`; every `address_line1_check` and `address_postal_code_check` came back `pass`.
- **Failing CVC values.** The guide says any CVC except `333` or `666` passes, implying those two fail. Both produced successful payments with `cvc_check: pass`.
- **Filtering events by delivery status.** `GET /v1/events?delivery_status=…` returned a 500 for every documented value. See [handling webhooks reliably](https://www.paydude.io/resources/handling-webhooks-reliably) for a sweep that does not need it.

Note also that the checks read `unchecked` on a freshly created payment method and are only filled in once it has been charged — so a test that inspects `card.checks` straight after tokenising sees nothing either way.

The practical answer is to test AVS and CVC handling at the unit level: record the response shapes, construct the failing ones yourself, and assert your code does the right thing with `address_postal_code_check: "fail"` without needing the sandbox to produce it.

## What no sandbox can simulate

The most expensive production failure in payments is a request that succeeds on the server and whose response is lost on the way back. Your code sees a timeout; the customer has been charged. The sandbox will never do this to you on request, so wrap your API client and do it yourself:

```javascript
// Test-only wrapper: performs the real call, then pretends it timed out.
// This is the exact failure an idempotency guard exists for.
function withLostResponses(api, { rate = 1 } = {}) {
  return {
    ...api,
    async post(path, body) {
      const result = await api.post(path, body);   // the write really happens
      if (Math.random() < rate) {
        throw Object.assign(new Error("ETIMEDOUT"), { code: "ETIMEDOUT" });
      }
      return result;
    },
  };
}

test("a lost response does not charge twice", async () => {
  const flaky = withLostResponses(sandboxApi, { rate: 1 });
  await expect(checkout(order, { api: flaky })).rejects.toThrow();

  // Retry with the real client, as your retry logic would.
  await checkout(order, { api: sandboxApi });

  const intents = await sandboxApi.get(
    `/v1/payment-intents?metadata[reference]=${order.reference}`);
  expect(intents.items).toHaveLength(1);
});
```

The Paydude API has no idempotency keys, so this test is the only evidence your own guard works — the pattern it exercises is in [idempotency keys explained](https://www.paydude.io/resources/idempotency-keys-explained). Adjust the metadata query to however your client encodes nested parameters.

The other things beyond any sandbox: how real issuers distribute their declines, real 3D Secure challenges on real cards, and real settlement timing. Sandbox payouts ran at about 12:00 UTC each day in our runs; that is the sandbox's schedule, not a promise about production. Plan a small live test with a real card and a real refund before launch, and watch the first week of production declines closely.

## Before you go live

1. Every row of the test matrix above passes.
2. The lost-response test passes and runs in CI.
3. Test-only code paths — raw card posting, amount overrides, fault injection — cannot run in production.
4. Production keys and the production account id are in production config only, and sandbox keys are nowhere near it.
5. The webhook endpoint is registered for production, verified with the production signing secret, and alerting on status changes.
6. One real payment and one real refund have been made and reconciled against a payout.

**Build against the sandbox first** An open sandbox, SDKs and an MCP server, on every tier. — [View pricing](https://www.paydude.io/pricing)

## Frequently asked questions

### What card number should I use to test payments?

4111 1111 1111 1111 with any future expiry and any CVC works in the Paydude sandbox, along with test Mastercard, Discover and American Express numbers. The card number does not control the outcome — the payment amount does.

### How do I simulate a declined card in the sandbox?

Create the payment intent for one of the trigger amounts between $7,777.01 and $7,777.16. $7,777.01 declines with insufficient_funds, $7,777.05 with expired_card and $7,777.15 with fraudulent. Smaller amounts such as $77.01 succeed.

### How do I test a dispute?

Use $7,777.97 for an inquiry, $7,777.98 for an inquiry that escalates, or $7,777.99 for a formal chargeback. When submitting evidence, the testing guide says an evidence description of REVERSAL wins the dispute and LOSS loses it.

### Can I test AVS and CVC failures in the sandbox?

The testing guide documents street-address prefixes and CVC values for this, but on the sandbox merchant we tested every address and CVC check came back as a pass. Test that handling at the unit level with constructed responses instead.

### How do I test what happens when an API response is lost?

Wrap your API client so it performs the real request and then throws a timeout, and assert that your retry logic does not create a second payment. No sandbox produces this failure on request, and it is the one most likely to double-charge a customer.

### How long do test ACH payments take?

In our sandbox run an ACH debit stayed in processing for about fourteen and a half hours before succeeding. Sandbox timing is not production timing, but it is long enough that ACH tests should exercise the webhook handler rather than wait for the payment.
