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

# Sandbox testing

> Verify payment, refund, payout, retry, and failure handling without moving real money

Sandbox requests create test objects and move no real money. Use your Sandbox
secret key. The amounts below select a Payment outcome, so you can reach each
one without special test data. Any other amount is answered by the Sandbox
processor itself and is not guaranteed to succeed, so treat an unexpected
failure on an ordinary amount as a Sandbox condition rather than a defect in
your integration, and use a rule amount whenever you need a specific result.
Create new test Payments and Refunds only in the
[supported payment currencies](/introduction#amounts-and-currencies); Sandbox does not
expand the accepted Payment or Refund currency set.

## Choose the outcome with the amount

To reach a specific outcome, send the Payment amount below; outside this list
the Sandbox processor answers on its own. A rule matches the `amount` integer
in minor units in every currency, whatever that currency's exponent, so send
`100051` itself rather than a converted value.

| Amount | Outcome | Result |
| - | - | - |
| `100005` | Declined by the bank | `FAILED`, `failure_code` `do_not_honor` |
| `100014` | Declined by the bank | `FAILED`, `failure_code` `invalid_number` |
| `100051` | Declined by the bank | `FAILED`, `failure_code` `insufficient_funds` |
| `100054`, or any other `100001`–`100099` | Declined by the bank without a more specific public reason | `FAILED`, `failure_code` `generic_decline` |
| `100100` | 3D Secure authentication fails | `FAILED`, `failure_code` `three_d_secure_failed` |
| `100101` | The processor cannot process the Payment | `FAILED`, `failure_code` `processor_error` |
| `100102` | Fraud controls decline a Direct Payment | `FAILED`, `failure_code` `fraud_filter` |
| `100103` | The Payment is left to the processor; a Refund of it fails | Refund `FAILED`, `failure.code` `processor_error` |

An outcome is decided when the Payment reaches its result, not when you create
it. A Direct or hosted-page Payment that becomes `REQUIRES_ACTION` selects its
outcome only after the payer completes the redirect, and an abandoned or
expired attempt keeps the status it ended with. Examples that never reach a
redirect and are terminal in the create response itself: `100102` on a Direct
Payment, and a request the processor refuses definitively. A processor failure
before execution can end a Direct Payment locally after eligible bounded
retries, with `processor_unavailable` or `processor_error`. These technical
failures are not issuer declines. If authentication or wallet execution may
already have reached the provider, Flowlix reconciles the existing order; an
unavailable read leaves it nonterminal. An uncertain hosted-page create can
return a `PENDING` Payment because a checkout may already exist. Retrieve the
existing Payment rather than creating a replacement while its result is unknown.
The processor's own page can show a success screen for a rule
amount while the Payment ends `FAILED`: fulfil on the retrieved Payment status,
never on what the payer saw.

The last two digits of an amount between `100001` and `100099` are the
processor response code. Today only `05`, `14` and `51` carry a more specific
public `failure_code`; every other code is a `generic_decline`, which is how
the same answer is treated for a real Payment. Branch on `failure_code`, never
on the amount you sent.

`100103` is the one rule that does not decide the Payment: create it, retrieve
it until it is terminal, and once it is `SUCCEEDED`, create a Refund of it. That
Refund, and every later Refund of the same Payment, fails in the create response
itself, so use a different Payment to test a successful Refund. Handle the
failure with the
[Refund failure procedure](/guides/operation-failures#refund-failures).

Each outcome produces the status, `failure_code`, `failure_message`, events and
webhooks that the same answer produces in Live, so what you build against here
is what you will see there. Handle a failed result with the
[failure codes](/guides/operation-failures) and a rejected request with the
[request errors](/guides/errors). The rules apply to Sandbox only and change
nothing in Live.

## What Sandbox does not reproduce

* **Payouts are not selected by amount.** The rules above apply to Payments and
  their Refunds; a Sandbox Payout follows the processor's own answer.
* **Card metadata is not decided by the amount.** The `country` returned on a
  Sandbox Payment comes from the Sandbox processor, and so does the `brand` on a
  hosted-page Payment; neither need match the card that was used, so do not
  assert on them.
* **A refusal by the processor is not a rule.** When the processor refuses the
  request outright, the Payment is created and ends `FAILED`, but the published
  code depends on the transport, not on the refusal: a Direct Payment ends with
  `generic_decline`, the same code the rest of the `100001`–`100099` band
  publishes, and a hosted-page Payment ends with `processor_error`, the same
  code `100101` publishes. On either transport the code alone does not tell a
  refusal apart from a rule.
* **`100102` is a Direct rule.** A hosted-page Payment with that amount follows
  the processor's own answer and is not declined by it.

## Card data

The outcome comes from the amount, not from the card. Never send a real card
number to Sandbox: use one of the synthetic examples below, with a future
expiry. They are not account-specific test fixtures.

| Network | Example test number |
| - | - |
| Visa | `4635440000002207` |
| Mastercard | `5520330000002205` |

For Hosted Payment Page, enter card data only in the hosted form. For Direct
API, submit it only from your PCI-compliant Sandbox backend using
`payment_method_data.card`. Supply the other required card fields as described
in the [Direct API guide](/guides/direct-api). Never copy a card request body
into logs, screenshots, issue trackers, or support messages.

## 3D Secure in Sandbox

A Direct Sandbox Payment usually becomes `REQUIRES_ACTION` with a
`next_action.redirect_url`, and the Sandbox authentication page completes
without a challenge screen. Treat the redirect as part of the flow: follow the
latest `next_action.redirect_url`, then retrieve the Payment and fulfil only on
`SUCCEEDED`, never on `return_url` alone.

To exercise a failed authentication, send `100100`. A completed authentication
page is not proof of payment, and the absence of a challenge screen is not
proof that authentication was skipped.

For each different scenario, create a new Payment with a new
`Idempotency-Key`.

## Wallet input and response checks

Apple Pay and Google Pay are Experimental: their support and this guidance are
still being refined and are not final. Direct processing accepts decrypted input
when the account and upstream wallet route are provisioned. The card scenarios
above do not provide usable wallet credentials or prove wallet authorization.
Use the [wallet input reference](/guides/direct-api#wallet-input-reference) for
local request-construction checks. Include the required Google `auth_method` and
the complete payer-browser object for PAN\_ONLY. Do not send real wallet
credentials to probe syntax or availability.

Before launch, test response parsing with `payment_method.card`, a known method
without card metadata, a null method, and an unfamiliar method string. A missing
`payment_method` field is invalid.
Test webhook handling with grouped Payment snapshots, including a present null
`payment_method` for an unknown method. Retrying an Event ID must preserve its
original content and must not repeat fulfilment. These local parsing checks do
not establish issuer authorization. A successful wallet payment test
additionally needs a provisioned account and a valid wallet credential supplied
through the approved upstream integration. See
[Payment response changes](/guides/payment-lifecycle#update-your-response-parser-before-launch)
and [Payment event snapshots](/guides/webhooks#payment-method).

## Verification checklist

<Steps>
  <Step title="Successful Payment">
    Complete one HPP or Direct Payment with an amount outside `100001`–`100103`
    and retrieve it until it is terminal. Confirm your order is fulfilled only
    after `SUCCEEDED`; if the attempt fails, create a new Payment and try again.
  </Step>

  <Step title="Terminal decline">
    Send `100051`, then `100054`. Confirm that your checkout branches on
    `failure_code`, handles both a specific reason and `generic_decline`, shows
    neutral customer copy, and creates a new Payment for another attempt.
  </Step>

  <Step title="3D Secure">
    Complete one Direct Payment through the
    [authentication page](#3d-secure-in-sandbox), then send `100100` for a
    failed authentication. Confirm that both use the returned Payment status,
    not the presence or absence of a challenge screen, to decide fulfilment.
  </Step>

  <Step title="Idempotency">
    Retry with the same key and an equivalent effective request identity, then
    change a non-card field such as `merchant_reference` while retaining the
    key. Confirm replay and `idempotency_key_reused` handling without recording
    card data.
  </Step>

  <Step title="Refunds">
    On a Payment created with an ordinary amount that reached `SUCCEEDED`,
    create a partial Refund and
    retrieve the parent Payment until that Refund is `SUCCEEDED` or `FAILED`.
    Only after `SUCCEEDED`, retrieve the Payment again, read
    the latest `amount_refundable`, and create a remaining Refund when the
    business still intends to return that amount. Then create a separate Payment with
    `100103`, let it reach `SUCCEEDED`, and confirm that a Refund of it fails:
    do not automatically issue another one, and follow the
    [Refund failure procedure](/guides/operation-failures#refund-failures).
  </Step>

  <Step title="Payouts">
    Submit one test Payout, retrieve it by ID, and locate it in the paginated
    Payout list. Keep `PENDING` and `PROCESSING` nonterminal; track
    `SUCCEEDED` or `FAILED` through retrieval and payout events. For
    `FAILED`, handle `failure_code` and `failure_message` without an
    automatic retry. A repeated creation can return its original response,
    so use GET for the latest state. See the [Payout guide](/guides/payouts).
  </Step>

  <Step title="Request failures">
    Exercise invalid authentication, malformed JSON, invalid fields,
    idempotency conflicts, and a safe transient retry. Preserve `Request-Id`
    without recording sensitive request data.
  </Step>

  <Step title="Webhooks">
    Create a Sandbox endpoint and subscribe to the Payment and Refund events
    you handle. Verify a real delivery, persist it before acknowledging, and
    confirm that repeating the same Event ID does not repeat fulfilment.
    Locally test altered payloads, an incorrect signing secret, and stale
    timestamps; these must fail signature verification. See
    [Webhooks](/guides/webhooks).
  </Step>
</Steps>

## Keep test evidence safe

Record operation, HTTP status, object status, machine-readable error code, and
`Request-Id`. Do not record a secret key, `Authorization` header, PAN, CVC,
full request body, or provider response.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.