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

# Sandbox Simulation Limits FAQ

> Exactly what the Waffo sandbox can and cannot reproduce: which reserved amounts trigger which error, why the refund amounts do nothing, how to simulate a wallet-side cancellation, why a simulated failed renewal never retries, and which scenarios have no sandbox path at all.

Getting in, and what the setup wizard changes, is on
[Sandbox Environment & Access FAQ](/docs/en/support/sandbox-environment-and-access-faq).
The simulator walkthrough is on
[Sandbox simulator](/docs/en/developer-docs/tools-and-references/developer-tools/sandbox-simulator)
and [Sandbox Testing FAQ](/docs/en/support/sandbox-testing-faq).

**This page is the boundary map**: which scenarios the sandbox can actually
produce, which it cannot, and where the reserved amounts do and do not apply. Read
it before you plan an acceptance run, so you do not spend a day trying to
reproduce something that has no sandbox path.

## What can and cannot be simulated? Give me the whole list.

| Scenario | Available in the sandbox? |
| - | - |
| Payment success | Yes — the simulation button on the checkout page |
| Payment failure / decline | Yes — the simulation button on the checkout page |
| Channel rejection (`C0005`) | Yes — a reserved order amount |
| System error (`C0001`) | Yes — a reserved amount, on create and on cancel |
| Unknown status (`E0001`) | Yes — a reserved amount, on create and on cancel |
| Idempotency conflict (`A0011`) | Yes — reuse a request ID with a different amount or currency |
| Refund amount exceeding the original (`A0003`) | Yes — request more than was paid |
| Order cancellation before payment | Yes — the cancel API |
| Subscription first-charge success and failure | Yes — the checkout page |
| Subscription renewal, success or failure | Yes — the subscription management page |
| Wallet-side (customer-initiated) cancellation | Yes, for supported payment methods — see below |
| Refund failure | Yes, synchronously — a create-family reserved amount on the refund request returns an error and leaves a `REFUND_FAILED` refund order; see below |
| Refund that first goes in progress and then fails | **No** — no simulation path |
| Chargeback / dispute | **No** — no simulation path |
| Channel timeout or no response | **No** — not directly; `E0001` is the closest analogue |
| Payout, withdrawal or settlement outcomes | **No** — the APIs exist, but there is no control to force an outcome |
| Time travel (advancing a trial end or an expiry date) | **No** — no such capability exists |
| 3DS challenge | **No** — no control forces a challenge, and the test card list has no 3DS card |

Everything in the "No" rows is genuinely absent rather than undocumented, so plan
around it: prove those code paths with unit tests against recorded payloads, and
verify the live behaviour in production once you are live.

## Which reserved amounts trigger which error, and on which endpoints?

The sandbox intercepts specific order amounts and returns the matching error
instead of processing normally. The mapping:

| Reserved amounts | Error returned | Applies to |
| - | - | - |
| `90`, `990`, `1990`, `19990` | `C0005` Payment Channel Rejection | Order create, subscription create |
| `9.1`, `91`, `991`, `1991`, `19991` | `C0001` System Error | Order create, subscription create |
| `9.2`, `92`, `992`, `1992`, `19992` | `E0001` Unknown Status | Order create, subscription create |
| `9.3`, `93`, `993`, `1993`, `19993` | `C0001` System Error | Order cancel, subscription cancel |
| `9.4`, `94`, `994`, `1994`, `19994` | `E0001` Unknown Status | Order cancel, subscription cancel |

Read the "applies to" column carefully, because it is where the mistakes happen:

* On **create**, the amount is taken from the request — the order amount on an
  order create, the amount on a subscription create.
* On **cancel**, you do not pass an amount. The amount matched is the one **stored
  on the order or subscription you are cancelling**. So to test a cancel error you
  must create the order at `9.3` first, then cancel it.
* These amounts are a **sandbox-only rule**. Nothing like this exists in
  production, where the same amounts are ordinary amounts.

## Why do the reserved refund amounts not work?

Because the refund path does not use the refund-specific amounts. The `9.5` and
`9.6` families listed for the refund API on the published
[Sandbox simulator](/docs/en/developer-docs/tools-and-references/developer-tools/sandbox-simulator)
page do **not** take effect — a refund of `995` is an ordinary refund.

What the refund path *does* react to are the **create** families: a
`refundAmount` equal to one of the `90` / `9.1` / `9.2` family values (the same
amounts that trigger `C0005`, `C0001` and `E0001` on order create) makes the
refund request fail at request time with an error of the matching class — a
channel or system error for the `90` and `9.1` families, an unknown-status error
for the `9.2` family. The refund order that was opened for the request finishes
as `REFUND_FAILED`, and a refund notification reporting the failure is sent to
your refund notify URL. That lets you prove your handling of a rejected refund
request and of a failed refund record; what it cannot give you is a refund that
first goes in progress and fails later, and there is no other way to force one in
the sandbox. What you *can* still prove on the refund path:

| Refund case | How |
| - | - |
| Successful refund, full and partial | Refund a paid sandbox order |
| Refund exceeding the original amount → `A0003` | Request more than was paid |
| Refund idempotency conflict → `A0011` | Reuse `refundRequestId` with a different amount or currency |
| Refund notification handling | The refund notification fires for the cases above, including the synchronous failure |

For a genuine refund *failure*, test your handler against a recorded payload
rather than trying to provoke one.

## My real prices land on the reserved amounts and my sandbox orders keep failing

This is a known situation and it has a fix that is not "change your prices":
**a sandbox merchant ID can be exempted from amount simulation.** Contact
integration support with your sandbox merchant ID and ask for the exemption; once
applied, reserved amounts behave as ordinary amounts for your sandbox.

Until then, the workaround is to avoid the five families above. In practice that
means avoiding amounts ending in `90`, and the `9.x` / `x91`–`x96` patterns —
adding a cent (for example `90.01` instead of `90`) is enough, because the match
is exact.

## Does the sandbox really send webhooks?

Yes. Sandbox webhook delivery follows the same contract as production — the same
signature, the same accepted response body, the same retry schedule. Everything
in [Webhook Delivery Contract FAQ](/docs/en/support/webhook-delivery-contract-faq)
applies unchanged.

Two things that are *not* different but get blamed for looking different:

* **Your `notifyUrl` must be publicly reachable.** The sandbox cannot reach
  `localhost` or a private address, and your TLS certificate must be publicly
  trusted. Use a tunnelling tool for local development.
* **Perceived sandbox delay is usually the retry schedule.** If a first delivery
  fails, the next attempts follow a growing back-off — see
  [Webhook retry](/docs/en/developer-docs/webhook/retry) for the schedule.
  A notification that "took minutes to arrive" almost always means the first
  attempts were rejected — check the Portal webhook log for the earlier failed
  attempts on the same record before concluding the sandbox is slow.

## Which test card numbers should I use?

Use the **quick-fill buttons on the sandbox checkout page** when a flow asks you
to enter a card. They populate the card number, an expiry and a CVV for a success
or a decline in one click, per card brand, for the brands your sandbox has
enabled — which removes both the transcription errors and the guesswork about
which value that environment expects.

If you need the values written down, the Portal's own list is at
**Merchant Portal → Integration → Test Cards**, with a copy action per card and
two scenario categories: **successful** and **failed**.

The table in [Sandbox Testing FAQ](/docs/en/support/sandbox-testing-faq) matches the
Portal list. The older published simulator page does not agree for every scheme,
so **prefer the in-product source over any document**, and prefer the checkout
quick-fill over both.

## What do I enter for expiry and CVV? Are they validated?

Any future expiry date, and any CVV of the right length — three digits, four for
the schemes that use four. The quick-fill on the checkout page supplies a valid
combination for you.

What this tells you about coverage: because expiry and CVV are not meaningfully
validated, the sandbox cannot prove your handling of an expiry-date rejection or
a CVV mismatch. Those belong to the "test against a recorded payload" list.

## The Portal test card page shows only success and failure. Where are the 3DS, insufficient-funds, CVC-error and risk-decline cards?

They do not exist. The Portal test card list has exactly two scenario categories,
successful and failed, and there is no card number that maps to a specific
decline reason.

The consequence for acceptance planning: the sandbox proves that your code takes
the right branch on success and on failure. It does not let you enumerate decline
reasons. Where your product behaves differently per decline reason — a retry
prompt for insufficient funds versus a "use another card" prompt for a hard
decline — drive those branches from `orderFailedReason` in unit tests, using the
codes in
[Error codes](/docs/en/developer-docs/tools-and-references/developer-tools/error-codes).

## How do I simulate a renewal, and can I chain periods?

From the subscription management page, which you obtain by calling
`POST /api/v1/subscription/manage` and opening the returned `managementUrl`. It
offers a simulated successful next payment and a simulated failed next payment,
and each click advances one period — so you can chain them: period 2 succeeds,
period 3 fails.

Preconditions, because these are what make the buttons unavailable:

| Requirement | Why the button is missing without it |
| - | - |
| The subscription is `ACTIVE` | Simulation is refused for any other status |
| The subscription is on a Waffo-managed billing schedule | A subscription whose billing is driven from the payment method's side simulates through a different path |
| You are in the sandbox | The simulation entrypoints do nothing in production |

If the page renders but the simulation controls are absent, check the
subscription's status first — an unactivated or already-cancelled subscription is
by far the most common cause.

## Will a simulated failed period then retry on the normal schedule?

**No — and this is the most important thing on this page.** Simulating a failed
renewal **sets the renewal retry allowance to zero first**, so the failed period
goes straight to its terminal state instead of entering the retry schedule.

Two consequences to plan around:

* **You cannot observe renewal retry behaviour in the sandbox** by simulating a
  failure. The retry schedule that runs in production is bypassed by the
  simulation itself.
* **The change persists for that sandbox subscription's configuration.** Renewal
  retries stay disabled afterwards, so a subscription you used to simulate a
  failure is no longer a clean subject for retry testing. Create a fresh
  subscription for anything retry-related.

To test your handling of the *outcome* of an exhausted retry sequence, the
simulated failure is exactly right — it produces the same terminal period state
and the same notification. To test the sequence itself, you need production
behaviour or a recorded set of payloads.

## Can I simulate the customer cancelling inside their wallet app?

Yes, for the payment methods where it is supported. The simulation produces the
same `SUBSCRIPTION_STATUS_NOTIFICATION` with `USER_CANCELLED` that a real
customer-side cancellation produces, so your handler sees the real thing.

Two gates decide whether it works for a given subscription:

1. **The subscription must be `ACTIVE`.**
2. **The payment method must support it.** Wallet-side cancellation is simulated
   by reproducing the message that payment method actually sends on
   cancellation, so it exists only for methods where that message is modelled.
   Where it is not, the call fails with a dedicated "this payment method does not
   support channel cancel" error rather than silently doing nothing — which is
   itself the answer to whether your method is covered: try it and read the
   error.

If your method is not covered, test the handler directly against a
`USER_CANCELLED` payload. The notification shape does not vary by payment method.

## Can I test a chargeback in the sandbox?

No. There is no chargeback or dispute simulation in the sandbox — no reserved
amount, no button, no endpoint.

What to do instead:

| Goal | Approach |
| - | - |
| Verify your chargeback webhook handler | Test against a recorded `CHARGEBACK_NOTIFICATION` payload; the field list is in [Webhook Payload Field FAQ](/docs/en/support/webhook-payload-field-faq) |
| Verify your evidence submission flow | Walk the API contract in [Chargeback field reference](/docs/en/support/chargeback-field-reference) |
| Understand what will happen in production | [Chargeback FAQ](/docs/en/support/chargeback-faq) and [Chargeback evidence guide](/docs/en/support/chargeback-evidence-guide) |

Also worth knowing before you plan around it: the chargeback notification is only
delivered if the Chargeback Notify URL is configured in the Portal, and it is
Portal-configured only — there is no request parameter. So the first thing to
verify is that the URL is set, in both environments.

## An order is stuck in an unknown state. How do I drive it to a final result?

`E0001` is returned at create time by a reserved amount, and the request is
rejected **before any order is created** — an inquiry on that request id comes
back as "order does not exist". That is the point: an unknown-status response
tells you nothing about whether an order exists, so the behaviour to prove is
your recovery logic — **treat the result as undetermined, inquire, and act on what
the inquiry returns; never mark the order failed or close it yourself**. When the
inquiry confirms nothing exists, re-submit with identical parameters (your
`paymentRequestId` makes that safe).

There is no sandbox control that produces an order which then sits in an
unknown state and flips to a final result later. So the behaviour to demonstrate
is the inquire-then-decide loop and the "did not self-close" property, not a
final state you produced by hand. The acceptance criteria phrase this as: do not
close the order, retry with identical parameters, and wait for the inquiry result
or the notification. See
[Integration acceptance criteria](/docs/en/developer-docs/tools-and-references/references/acceptance-criteria).

## Do I need a network exit in a particular country to test?

Not for the sandbox. Nothing in the sandbox payment path checks where your
request or your browser comes from, and because the sandbox does not connect to
real payment channels there is no channel-side geographic restriction to satisfy
either. A sandbox run from anywhere is a valid sandbox run.

Where geography does matter is production verification of methods that genuinely
depend on the customer's region — device or account region settings for the
mobile wallet products, and the supported-country lists those products publish.
Those checks are listed in
[Sandbox and testing](/docs/en/developer-docs/getting-started/sandbox) under the
per-payment-method sections, and they are production checks, not sandbox ones.

## Which things can only be verified in production?

Keep this list next to your go-live plan, because each item is a place where a
green sandbox run proves nothing:

| Only verifiable in production | Why |
| - | - |
| Real payment method behaviour and its own screens | The sandbox shows a simulator, not the real payment page |
| Decline reasons and their distribution | The sandbox has success and failure only |
| Renewal retry behaviour | Simulating a failure disables retries |
| Refund failures, chargebacks | No sandbox path |
| Payout, withdrawal and settlement outcomes | No outcome controls in the sandbox |
| Mobile wallet device, account and region requirements | Depend on real devices and accounts |
| Production access approvals for the mobile wallet products | Granted by the wallet provider, not by Waffo |
| Your production credentials, host and callback URL | Different values from the sandbox by design |

The mobile wallet checks in particular have their own prerequisite lists —
accounts, cards, device regions, in-app browser configuration, and a production
access request that must be approved before launch. Work through
[Sandbox and testing](/docs/en/developer-docs/getting-started/sandbox) and
[Payment method integration notes](/docs/en/developer-docs/tools-and-references/references/payment-method-integration-notes)
rather than discovering them at cutover.

## Do I have to test every contracted payment method individually?

No. The acceptance criteria set out representative coverage rather than
exhaustive coverage: at least one card, at least one per country per type, at
least one virtual account, every method with special required parameters, and
manual device testing for the mobile wallets. The full rule set is in
[Integration acceptance criteria](/docs/en/developer-docs/tools-and-references/references/acceptance-criteria).

Two follow-ups that come up constantly:

* **A method your agreement does not allow refunds on** does not need a refund
  case. Run the conditional cases only for the APIs and behaviours your
  integration actually includes — the templates mark them as conditional for that
  reason.
* **A second integration is not a full re-run.** Once you have passed acceptance
  at least once, a subsequent one-time-payment integration uses the additional
  integration template, which covers only the delta. Your first integration
  always uses the standard template.

***

Next: [Go-live checklist](/docs/en/developer-docs/getting-started/go-live) for the
switch itself, and
[Sandbox Environment & Access FAQ](/docs/en/support/sandbox-environment-and-access-faq)
for what does and does not carry over from the sandbox.


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