> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tesouro.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Test your integration with Tesouro using test card numbers, simulated responses, and the Sandbox environment.

export const CardNumberGenerator = () => {
  const BIN_OPTIONS = [{
    value: "378282",
    label: "American Express (USA)"
  }, {
    value: "411113",
    label: "Visa (USA)"
  }, {
    value: "511111",
    label: "MasterCard (USA)"
  }, {
    value: "601100",
    label: "Discover (USA)"
  }];
  const RESPONSE_OPTIONS = [{
    value: "0000",
    label: "Approved"
  }, {
    value: "0001",
    label: "Partially Approved"
  }, {
    value: "1001",
    label: "Call Issuer"
  }, {
    value: "1002",
    label: "Invalid Account Number"
  }, {
    value: "1005",
    label: "Generic Decline"
  }, {
    value: "1006",
    label: "Insufficient Funds"
  }];
  const AVS_OPTIONS = [{
    value: "00",
    label: "Address and Postal Code Matched"
  }, {
    value: "02",
    label: "Address Matched, Postal Code Mismatched"
  }, {
    value: "04",
    label: "Address Matched, Postal Code Unverified"
  }, {
    value: "08",
    label: "Address and Postal Code Mismatched"
  }];
  const CVV_OPTIONS = [{
    value: "0",
    label: "Matched"
  }, {
    value: "1",
    label: "Mismatched"
  }, {
    value: "2",
    label: "Not Processed"
  }, {
    value: "3",
    label: "Not Verified"
  }];
  const KNOWN_BINS = BIN_OPTIONS.map(o => o.value);
  const calculateLuhn = str => {
    let sum = 0;
    let shouldDouble = true;
    const reversed = str.split("").reverse().join("");
    for (let i = 0; i < reversed.length; i++) {
      let digit = parseInt(reversed.charAt(i), 10);
      if (shouldDouble) {
        digit *= 2;
        if (digit > 9) digit -= 9;
      }
      sum += digit;
      shouldDouble = !shouldDouble;
    }
    return (10 - sum % 10) % 10;
  };
  const generateCardNumber = (binVal, respVal, avsVal, cvvVal) => {
    const cleanBin = binVal.replace(/\D/g, "");
    let partial = cleanBin + respVal + avsVal + cvvVal;
    const targetLength = 16;
    if (partial.length > targetLength - 1) {
      partial = partial.slice(0, targetLength - 1);
    }
    while (partial.length < targetLength - 1) {
      partial += "0";
    }
    const checkDigit = calculateLuhn(partial);
    const fullPan = partial + checkDigit;
    return fullPan.match(/.{1,4}/g)?.join(" ") || fullPan;
  };
  const [bin, setBin] = useState("378282");
  const [resp, setResp] = useState("0000");
  const [avs, setAvs] = useState("00");
  const [cvv, setCvv] = useState("0");
  const [isEditing, setIsEditing] = useState(false);
  const [copied, setCopied] = useState(false);
  const [cardNumber, setCardNumber] = useState("");
  const inputRef = useRef(null);
  useEffect(() => {
    setCardNumber(generateCardNumber(bin, resp, avs, cvv));
  }, [bin, resp, avs, cvv]);
  useEffect(() => {
    if (isEditing && inputRef.current) inputRef.current.focus();
  }, [isEditing]);
  const handleManualBinChange = e => {
    const val = e.target.value.replace(/\D/g, "");
    if (val.length <= 6) setBin(val);
  };
  const copyToClipboard = () => {
    if (!cardNumber) return;
    navigator.clipboard.writeText(cardNumber.replace(/\s/g, "")).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const binOptions = KNOWN_BINS.includes(bin) ? BIN_OPTIONS : [...BIN_OPTIONS, {
    value: bin,
    label: "Custom (" + bin + ")"
  }];
  const displayBin = cardNumber ? cardNumber.slice(0, 7) : "0000 00";
  const displayRest = cardNumber ? cardNumber.slice(7) : "00 0000 0000";
  const selectStyle = {
    padding: "10px 36px 10px 12px",
    border: "1px solid var(--border)",
    borderRadius: 8,
    backgroundColor: "var(--grayscale-a2, rgba(0,0,0,0.03))",
    color: "var(--grayscale-a12)",
    fontSize: "0.875rem",
    outline: "none",
    cursor: "pointer",
    width: "100%",
    WebkitAppearance: "none",
    MozAppearance: "none",
    appearance: "none",
    backgroundImage: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E\")",
    backgroundRepeat: "no-repeat",
    backgroundPosition: "right 12px center",
    backgroundSize: "12px"
  };
  const labelStyle = {
    marginBottom: 6,
    fontWeight: 600,
    fontSize: "0.75rem",
    color: "var(--grayscale-a11)",
    textTransform: "uppercase",
    letterSpacing: "0.05em"
  };
  const fieldStyle = {
    display: "flex",
    flexDirection: "column",
    flex: "1 1 48%",
    minWidth: 200
  };
  const renderSelect = (label, value, onChange, options) => <div style={fieldStyle}>
      <label style={labelStyle}>{label}</label>
      <select value={value} onChange={e => onChange(e.target.value)} style={selectStyle}>
        {options.map(opt => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
      </select>
    </div>;
  return <div style={{
    margin: "2rem 0"
  }}>
      <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: 12
  }}>
        {renderSelect("BIN", bin, setBin, binOptions)}
        {renderSelect("Transaction response", resp, setResp, RESPONSE_OPTIONS)}
        {renderSelect("AVS response", avs, setAvs, AVS_OPTIONS)}
        {renderSelect("Security code response", cvv, setCvv, CVV_OPTIONS)}
      </div>

      <div style={{
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    marginTop: 24,
    padding: 20,
    borderRadius: 12,
    border: "1px solid var(--border)",
    backgroundColor: "var(--grayscale-a2, rgba(0,0,0,0.02))",
    minHeight: 80
  }}>
        <span style={{
    fontFamily: "var(--font-code, ui-monospace, monospace)",
    fontSize: "1.125rem",
    letterSpacing: "0.05em",
    color: "var(--grayscale-a12)",
    display: "flex",
    alignItems: "center"
  }}>
          {isEditing ? <input ref={inputRef} type="text" value={bin} onChange={handleManualBinChange} onBlur={() => setIsEditing(false)} onKeyDown={e => e.key === "Enter" && setIsEditing(false)} maxLength={6} style={{
    width: "5rem",
    backgroundColor: "var(--grayscale-a3)",
    color: "var(--grayscale-a12)",
    border: "2px solid var(--accent-a9, #8b5cf6)",
    borderRadius: 6,
    padding: "2px 4px",
    outline: "none",
    textAlign: "center",
    fontFamily: "inherit",
    fontSize: "inherit",
    letterSpacing: "inherit",
    marginRight: 2
  }} /> : <span onClick={() => setIsEditing(true)} title="Click to enter a custom 6-digit BIN" style={{
    color: "var(--accent-a11, #8b5cf6)",
    textDecoration: "underline",
    textDecorationStyle: "dashed",
    textUnderlineOffset: 4,
    textDecorationThickness: 2,
    cursor: "pointer"
  }}>
              {displayBin}
            </span>}
          <span>{displayRest}</span>
        </span>

        <button type="button" onClick={copyToClipboard} style={{
    marginLeft: 16,
    display: "inline-flex",
    alignItems: "center",
    gap: 6,
    padding: "4px 12px 4px 8px",
    borderRadius: 999,
    border: "1px solid var(--border)",
    backgroundColor: "var(--grayscale-a2, rgba(0,0,0,0.02))",
    color: "var(--grayscale-a11)",
    fontSize: "0.75rem",
    fontWeight: 500,
    cursor: "pointer",
    transition: "background-color 0.15s"
  }}>
          {copied ? <span style={{
    color: "var(--accent-a11, #8b5cf6)",
    fontWeight: 700
  }}>Copied!</span> : <span style={{
    display: "inline-flex",
    alignItems: "center",
    gap: 6
  }}>
              <svg viewBox="0 0 20 20" aria-hidden="true" style={{
    height: 14,
    width: 14,
    opacity: 0.7
  }}>
                <path fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" d="M3.5 6v10a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1l-.447.894A2 2 0 0 1 11.263 6H8.737a2 2 0 0 1-1.789-1.106L6.5 4h-1a2 2 0 0 0-2 2Z" />
                <path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" d="m13.5 4-.447.894A2 2 0 0 1 11.263 6H8.737a2 2 0 0 1-1.789-1.106L6.5 4l.724-1.447A1 1 0 0 1 8.118 2h3.764a1 1 0 0 1 .894.553L13.5 4Z" />
              </svg>
              Copy
            </span>}
        </button>
      </div>
    </div>;
};

## Test card numbers

Use the generator below to create test card numbers for the Sandbox environment. Each card number encodes a specific combination of transaction response, AVS result, and security code verification outcome — allowing you to simulate different processing scenarios.

Select your desired responses from the dropdowns, or click the BIN portion of the generated number to enter a custom 6-digit BIN.

<CardNumberGenerator />

<Info>
  Test card numbers are only valid in the **Sandbox** environment. They will be rejected in
  production.
</Info>

Use your product's Sandbox environment to test your integration with Tesouro. All test scenarios described on this page are designed to run in a non-production environment — they will not work in production.

## Common test scenarios

When you first connect to Tesouro in Sandbox, validate the following end-to-end flows:

* **Create a test transaction** using your product's standard UI or API calls, and confirm that Tesouro accepts the request, returns the expected response payload and status, and persists the transaction in your reporting or back-office tools.
* **Confirm a transaction lifecycle** from creation through completion (for example: created → pending → succeeded/failed) and verify that each status update is surfaced correctly in your product and stored in your own database or logs.
* **Test idempotency and retries** by resending the same request with the same idempotency key and confirming that no duplicate transactions are created and responses remain consistent across retries.

Run these scenarios with several combinations of amounts, currencies (where applicable), and customer data to cover typical production usage.

## Error simulation

In addition to happy-path flows, verify how your integration behaves when Tesouro returns errors or delays:

* **Simulate declines** — Use documented test data that causes Tesouro to return common decline codes. Confirm that your product surfaces a clear error message and logs the decline reason.
* **Simulate timeouts** — Configure requests that intentionally exceed time limits or use Tesouro-provided test parameters that trigger delayed responses. Verify that your integration applies reasonable timeouts and implements safe retries without creating duplicate transactions.
* **Test validation errors** — Send requests with missing required fields, invalid formats, or unsupported parameter values. Confirm that Tesouro returns structured error details and your integration parses and logs them.

## Webhook testing

If your integration relies on webhooks for asynchronous events (for example, transaction status updates or payout events), use Sandbox to validate your webhook consumer:

* **Configure a Sandbox webhook endpoint** — Register a dedicated non-production URL with Tesouro for receiving events.
* **Verify event handling** — Confirm that your service validates webhook signatures (if provided), parses the payload, and handles re-delivered events idempotently.
* **Test retries and failure behavior** — Temporarily configure your endpoint to return non-2xx responses and verify how Tesouro retries webhook deliveries in Sandbox.
