> ## 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.

# Limits, Currency & Configuration Errors

> The order-creation rejections not covered elsewhere: A0008 vs A0020 vs A0021 amount limits, why an uncontracted currency returns A0018 rather than A0007, A0026/A0047 checkout language, A0024 expired FX rate (not A0023), A0014 cross-currency refunds that round to zero, A0022 ambiguous payment method — plus why limits do not move with exchange rates, why Settlements can be missing from your Portal, sandbox versus production availability, and recurring-payment rules.

[Common Errors Troubleshooting](/docs/en/support/errors-troubleshooting-guide) covers
the codes that generate the most tickets: `A0001`, `A0002`, `A0003`, `A0018` and
the `E`xxxxx family. This page covers the rest of the order-creation
rejections — limits, currency, language, FX and configuration — and then the
configuration questions that produce the same "why can't I…" tickets without
producing an error code at all.

## Which error code means which limit? I have three different ones

They are three different ceilings, checked at different levels. Reading them as
the same thing is the usual reason a merchant "fixes" the wrong setting.

| Code | Message | What it means | Where the limit lives |
| - | - | - | - |
| `A0008` | Payment amount should be within the range | The order amount is outside the min/max range of the **payment method** for that currency | Payment-method configuration |
| `A0020` | Payment amount exceeds transaction limit | The **single order** exceeds a per-transaction limit on your account for that order currency | Account-level limit, per order currency |
| `A0021` | Accumulative payment amount exceeds daily transaction limit | This order **plus everything already taken today** in that order currency exceeds a daily ceiling | Account-level limit, per order currency |

Three things follow:

* `A0020` and `A0021` are **per order currency**. Raising one currency's limit
  does nothing for another.
* `A0021` depends on the volume already taken today, so the same request can
  succeed in the morning and fail in the evening. Retrying immediately will not
  help; retrying the next day will.
* `A0008` is a payment-method range, not an account limit. Where to read the
  ranges is covered in
  [Common Errors Troubleshooting](/docs/en/support/errors-troubleshooting-guide).

If either account-level limit is set too low for your business, it is a
configuration change — contact your Waffo integration contact.

## I sent a currency I am not contracted for. Do I get `A0007`?

No — you get **`A0018`** *"Payment method in merchant order does not match with
current merchant contract"*. There is no currency-specific rejection on the
ordinary order-creation path: the contract lookup matches on your merchant ID
**and** the order currency together, so a currency none of your agreements carry
simply leaves no agreement to match, and the failure is reported as a
payment-method mismatch. The message misleads; the cause is the currency.

`A0007` *"Transaction currency is not supported"* does exist in the error table,
but it is reserved for a specialised stablecoin-protocol payment path that only
accepts one settlement currency. If you are creating a normal order and see a
currency problem, the code you are debugging is `A0018`.

| Symptom | Code |
| - | - |
| The order currency is not contracted | `A0018` |
| The payment method, or the `orderCurrency` → `userCurrency` pair, is not contracted | `A0018` |
| The amount is outside the contracted range | `A0008` |

`/api/v1/paymethodconfig/inquiry` returns one entry per contract, each with its
own `currency` — match on the currency you intend to charge in before you build
the request. The currency-shaped ways to earn an `A0018` are worked through in
[Do I Have to Enable a Currency?](/docs/en/support/currency-and-method-enablement-faq).

## `A0026` and `A0047` — what is wrong with my `cashierLanguage`?

They separate "you asked for a language we do not have" from "that is not a
valid language tag".

| Code | Meaning | Typical cause |
| - | - | - |
| `A0026` | The checkout language you specified is not supported | A valid tag for a language Waffo does not serve |
| `A0047` | The checkout language format is invalid | Wrong syntax, or extra subtags |

The value must be an IETF BCP 47 tag in the form
`language[-script][-region]`:

* **language** — a two-letter ISO 639-1 code, and it must be one Waffo supports.
  The supported set cannot be extended by passing something else.
* **script** — optional, four letters (ISO 15924). If present it must be a
  script Waffo uses; `Hant` and `Hans` are the ones in service.
* **region** — optional, a two-letter ISO 3166-1 alpha-2 code. Any valid country
  code is accepted here; the region is not restricted to a supported list.

Anything else is `A0047`: underscores instead of hyphens (`en_US`), variant
subtags (`de-DE-1996`), and private-use extensions (`en-US-x-foo`) are all
rejected as malformed rather than unsupported. Casing is not an error — `en-us`
is normalised to `en-US`.

## Cross-currency order rejected because of the exchange rate — which code is it?

**`A0024`** — "Order can not be processed due to the fx rate is expired."

> [!WARNING]
>
> `A0024` is the code, not `A0023`. `A0023` is **"Order cannot be created due to
> expired token"** — an entirely different failure, about a card token, with an
> entirely different fix. If you have integration notes that map an expired or
> worsened FX rate to `A0023`, they are wrong; correct them.

On a cross-currency order the quoted rate is validated at order creation. If the
quote is no longer valid — it expired, or the current rate has moved against the
quote — the order is **rejected outright**. Waffo does not silently reprice the
order at the new rate, because that would change what the user pays after you
committed to a price.

What to do:

1. Treat `A0024` as retryable. Re-create the order; it picks up a current quote.
2. Do not carry a quote across a long checkout funnel. The longer the gap
   between quoting a price and creating the order, the more often you will see
   this.
3. Show the user the refreshed amount if your UI displayed the local amount
   before order creation.

How rates are quoted and for how long they hold is in
[Exchange Rates in Payments & Settlement](/docs/en/support/exchange-rates-faq).

## `A0023` vs `A0024` — a summary I can paste into my code comments

| Code | Message | Subject | Fix |
| - | - | - | - |
| `A0023` | Order cannot be created due to expired token | A card `tokenId` is no longer usable | Re-tokenize the card; see [Tokenization & Card Payments](/docs/en/support/tokenization-faq) |
| `A0024` | Order can not be processed due to the fx rate is expired | The FX quote on a cross-currency order | Re-create the order to pick up a current rate |

## My cross-currency partial refund was rejected with `A0014`. The amount looked fine

Check the amount **after conversion**. On a cross-currency order the refund
amount you send is converted before validation, and if the converted amount
comes out as **zero or less** the refund is rejected with `A0014` ("Order cannot
be refunded due to refund rules").

This bites in two situations:

* A very small partial refund on a currency pair with a large rate — the
  converted amount rounds down to zero.
* A currency with fewer minor units than you assumed, so the converted amount
  truncates away entirely.

Note what `A0014` does **not** cover: asking for more than is left. A refund
whose amount, added to everything already refunded or in progress, exceeds the
original payment is rejected as a plain parameter failure — `A0003 Parameter
validation failed` — not as a refund-rule breach. So on a cross-currency order,
read `A0014` as "the converted amount did not survive the refund rules" and
`A0003` as "you asked for too much"; in both cases check the figure after
conversion rather than the one you sent. The conversion rules are in
[Exchange Rates](/docs/en/support/exchange-rates-faq); the per-method refund rules
are in
[Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq).

## `A0022` "multiple products are found" — what am I supposed to specify?

The product. `A0022` means the payment method you asked for resolves to **more
than one contracted product** on your account — for example the same method
signed both for one-off payments and for subscriptions. Waffo will not guess
which one you meant, so the order is rejected instead of being created against
the wrong product.

The fix is to send `productName` in `paymentInfo` so exactly one contract
matches. See
[Checkout customization](/docs/en/developer-docs/integration/checkout/customization)
for the parameter combinations, including how `payMethodType`, `payMethodName`
and `payMethodCountry` narrow the match.

## Do my signed amount limits change when the exchange rate moves?

No. The minimum and maximum order amounts on your contract are **fixed values**.
They are calculated once, when the payment method is signed for your account,
and they do not move with the exchange rate afterwards — including on
cross-currency orders.

Two details worth knowing:

* When your pricing currency differs from the payment method's own currency, the
  bounds are converted at signing time and then held: the minimum is rounded
  **down** to the currency's minor units (never to zero — it floors at one minor
  unit), and the maximum is rounded **up**.
* A limit you request for your account has to sit **inside** the range the system
  calculated. Your range is a subset of it, never wider. If you ask for a wider
  range, the configuration is rejected rather than clamped.

So a rate movement never explains an amount rejection. If an amount used to pass
and now fails, look at the account-level daily limit (`A0021`) or a
configuration change, not at FX. Where to read the values is in
[Common Errors Troubleshooting](/docs/en/support/errors-troubleshooting-guide).

## The payment failed because of a limit, but not one of mine. What is that?

Some wallet payment methods enforce limits on the **user's own wallet** — most
commonly an annual ceiling on how much the user may spend through it. When the
user hits it, the payment fails, and there is nothing you or Waffo can configure
to allow it.

How to handle it:

* Show the user a neutral message and offer another payment method rather than
  retrying the same one.
* Do not count these against your own limits or your integration's health; they
  are user-side.
* Do not attempt to split the order into smaller payments to get under the
  ceiling — an annual ceiling is not evaded by splitting, and it degrades the
  user's experience.

## Why can't I see Settlements, Fees or Settlement Cycle in my Portal?

Because of what you have signed. Parts of the Merchant Portal are driven by your
contracted products, not by your role — an Admin cannot grant them. See
[Merchant Portal Operations](/docs/en/support/portal-operations-faq) for the
menu-level table.

On top of the menus, merchants on a **gateway-only** arrangement — where the
signed product is Direct Payment and nothing else — also lose settlement-related
**columns**:

| Where | What is hidden |
| - | - |
| PayIn (payment-method configuration) | `Fees` and `Settlement Cycle` columns |
| Refunds list | `Settlement Status` column |
| Navigation | Settlements, Balance, Withdrawal and the related submenus |

This is expected rather than a defect: with no settlement product there is no
settlement cycle, no fee schedule to display, and no settlement status to report.
Nothing about it is switchable from your side. Order and refund data, exports and
webhooks are unaffected.

If you expected to see settlement data and do not, the question to ask support is
"which products is my account signed for", not "please grant me the menu".

## A payment method works in the sandbox but fails in production

That is possible by design, and it is the one sandbox difference that surprises
people the most. **The sandbox deliberately carries payment methods and
configurations that are not live in production**, so that integration and
end-to-end testing can start before a method is switched on for real traffic.

So:

* **Sandbox availability is not evidence of production availability.**
* Confirm production coverage against your contract — the Portal's PayIn page,
  or `/api/v1/paymethodconfig/inquiry` against production — not against sandbox
  behaviour.
* A method that returns `A0018` in production while working in the sandbox is
  usually this, not an integration bug.

Other sandbox/production differences are in
[Sandbox Testing](/docs/en/support/sandbox-testing-faq).

## How does a user authorize a recurring WeChat Pay charge?

Through one of two flows, chosen by where the user is:

| User's context | Signing flow |
| - | - |
| Desktop web | The user scans a code to authorize |
| Mobile browser | The user authorizes in an H5 page |

Once the authorization exists, Waffo charges the recurring amounts against it
without further user action, the user or you can terminate the authorization, and
charged orders can be refunded in full or in part. Subscription mechanics
themselves — plan changes, failed deductions, retries, notifications — are in
the [Subscription FAQ](/docs/en/support/subscription-faq).

Only the two flows above exist. If you are building against a design that also
lists in-app, mini-program or in-client authorization, those are not available.

## Can a refund on the first payment stop a whole recurring series?

Yes, on card recurring series, and this is the failure mode most likely to
surprise you months later.

| What happens to the first payment of the series | Effect on the series |
| - | - |
| **Fully refunded** | The series **terminates** — no further recurring charges can be made against it |
| Referenced by a fraud or dispute transaction | The series **terminates** |
| **Partially refunded** | No effect — the series continues |

So a customer-service full refund of a first charge silently ends the customer's
subscription at the payment level, even though your own records still show it as
active. Two safeguards:

1. Prefer a partial refund where the goodwill amount allows it.
2. When you do fully refund a first charge, treat the series as ended and
   re-establish the authorization if the customer stays.

## Do I need to tell the checkout what device the user is on?

No. The Waffo checkout detects the user's device and chooses the presentation
itself:

| User's device | What the checkout does |
| - | - |
| Mobile browser | Redirects into the payment app or page |
| Desktop browser | Renders a QR code for the user to scan |

You do not pass any device, terminal or authentication-mode parameter for this —
there is nothing to get wrong, and nothing to keep in sync when a payment method
changes its behaviour. Some app-based wallet methods take it one step further on
mobile: the checkout either deep-links straight into the specific app the user
picked, or hands off to the operating system's app chooser.

What you *do* control is which payment methods appear at all — see
[Checkout customization](/docs/en/developer-docs/integration/checkout/customization)
and [Checkout Display Rules](/docs/en/support/cashier-display-rules-faq).

## I charged a card without tokenizing it. Can I charge it again later?

No. A card payment made without a `tokenId` is a **one-off customer-initiated
transaction**. Nothing is retained that would let you charge that card again, so
the payment has no merchant-initiated follow-up capability.

To charge a card again later you must tokenize first: complete the tokenization
flow, hold a valid `tokenId`, and reference that token on the subsequent charges.
There is no way to convert a completed plain-card payment into a token after the
fact — the user has to go through binding.

The full token lifecycle, the CIT/MIT distinction and the
`AUTHORIZATION_REQUIRED` case are in
[Tokenization & Card Payments](/docs/en/support/tokenization-faq).

## Where do I look next?

| Question | Page |
| - | - |
| `A0001`, `A0002`, `A0003`, `A0018`, `E`xxxxx, webhook retries | [Common Errors Troubleshooting](/docs/en/support/errors-troubleshooting-guide) |
| Which rate a cross-currency order uses and for how long | [Exchange Rates in Payments & Settlement](/docs/en/support/exchange-rates-faq) |
| Per-method refund rules behind `A0017` and `A0014` | [Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq) |
| Portal menus, roles and why they differ between merchants | [Merchant Portal Operations](/docs/en/support/portal-operations-faq) |
| Sandbox credentials, simulators and test cards | [Sandbox Testing](/docs/en/support/sandbox-testing-faq) |
| Tokens, CIT and MIT | [Tokenization & Card Payments](/docs/en/support/tokenization-faq) |


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