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

# Non-Card Tokens & Token Failure Reasons

> The parts of tokenization the card FAQ does not cover: token types beyond CARD, the extra fields and fixed expiry of wallet-class tokens, why a tokenization submit is rejected, the cardholder-name rule, how MIT behaves differently from CIT, why a card payment can be refused before it is sent anywhere, and self-service management of saved payment methods.

[Tokenization & Card Payments](/docs/en/support/tokenization-faq) is the main page:
what a token is, how to bind a card, the token lifecycle, session and token
validity for cards, CIT versus MIT at the request level, and setting a default
card. **Read that first.**

This page is the supplement. It answers the questions that come up once you are
past a basic card integration: tokens that are not cards, the business reasons a
tokenization attempt is refused, the behavioural differences of merchant-initiated
charges, and the limits that can stop a card payment before it goes anywhere.

## Is `tokenType` always `CARD`?

No. `CARD` is the value most integrations use and the only one the public
specification documents, but `tokenType` is a general field: the token service
maintains a set of types covering non-card instruments as well, and
**wallet-class tokens** in particular follow different rules for required fields
and for expiry (both below).

What you should do with that:

* **Do not hardcode `"CARD"`** as the only value your code can produce or parse.
  Read the `tokenType` back from inquiry responses and notifications, and handle
  an unfamiliar value by storing it rather than failing.
* **Do not assume a type is available to you.** Which types your account can
  actually tokenize follows your payment-method configuration. An unsupported
  type is rejected at generate time as a parameter error.
* Ask your Waffo integration contact which types apply to the payment methods
  you have, rather than probing the API.

## What does a wallet-class token need that a card token does not?

Three extra fields inside the token data, all mandatory:

| Field | Why |
| - | - |
| `payMethodName` | Which payment method the token belongs to |
| `payMethodCountry` | Which country's configuration of that method |
| `orderId` | The order during which the user authorised the binding |

Miss any of them and generate fails as `A0003` **Parameter validation failed**.

The `orderId` requirement is the structural difference worth understanding: a
card can be tokenized standalone, whereas a wallet binding is something the user
authorises **in the course of paying**. So the wallet token is created against a
real order, not in a vacuum. In practice you request it as part of a payment
rather than as a separate "add a payment method" step.

## How long does a wallet token last?

| Token class | Expiry |
| - | - |
| Card | The **earlier** of the platform's fixed token validity period from creation, or the last moment of the card's own expiry month |
| Wallet class | The **same fixed** platform validity period from creation, truncated to the end of that day |

So a wallet token has no equivalent of the card-expiry ceiling — there is no
underlying expiry date to respect — and the expiry lands on a day boundary
rather than a month boundary.

Two things follow:

* **Do not derive a wallet token's remaining life from card logic.** There is no
  `expiry` field in the token data to reason about.
* The token can still stop working long before its calculated expiry, because
  the user unlinked the authorisation on the wallet side. That is invisible to
  you until a charge fails. Handle `A0037` (**the user payment token is invalid
  or has expired**) as a prompt to re-authorise, not as a transient error.

## If the same user authorises the same wallet again, do I get the same token?

Not necessarily — and this is where wallet tokens differ from cards most
sharply. The re-use key for a wallet-class token includes the **order** the
binding happened on, alongside your merchant ID (and sub-merchant ID where you
send one), your `merchantUserId`, the token type and the payment method.

Because a new authorisation happens on a new order, a second binding of the same
wallet by the same user generally produces a **new** `tokenId`. Card tokens
behave differently: their key is based on the credential itself, so re-binding
the same card returns the existing token (see the card FAQ).

Design consequences:

* **Key your storage on `tokenId`**, and expect a user to accumulate more than
  one wallet token over time.
* **Show the user a manageable list** rather than a growing pile: suspend or
  unbind the ones they no longer want (last section on this page).
* **Do not use the presence of a token as proof the user has exactly one wallet
  binding.**

## What must I send on inquiry and remove for a non-card token?

Both endpoints have a conditional shape rather than one required identifier:

| Call | Rules |
| - | - |
| `/api/v1/tokenization/inquiry` | Supply at least one of `tokenId`, `merchantUserId`, `tokenRequestId`. When `tokenId` is absent, `tokenType` becomes **required** — without a token ID, the type is what scopes the lookup. |
| `/api/v1/tokenization/remove` | `tokenType` is **required**, plus at least one of `tokenId` or `merchantUserId`. |

The practical read: `tokenType` is not decoration on these two calls. If you
have built your integration around cards and pass no `tokenType`, adding a
second token class will break your lookups in a way that looks like "the token
does not exist".

All three token endpoints also assert that the `merchantId` in the body matches
the merchant ID your API key is issued for, and reject a mismatch as `A0003`
with the detail `mid in params is inconsistent with apiKey`.

## Why was my tokenization submit rejected?

Beyond signing and field-shape problems, there are three **business** reasons a
card submission is refused. All three are decided before anything is stored, and
all three are permanent for that input — retrying the identical data will fail
identically.

| Reason | What it means | What to do |
| - | - | - |
| The card's issuing range is not recognised | Waffo could not identify the card from its leading digits | Ask the user to check the number, or to use a different card. Do not retry the same digits. |
| The card's scheme or type is not covered by your configuration | The card is real, but that scheme or product type is not enabled for your account | Reported as `A0031` **Card scheme or type provided by Merchant cannot be supported**. This is a configuration question for your Waffo contact, not a data problem. |
| The expiry date is invalid or already past | The expiry must be `MM/YYYY` with a month of `01`–`12`, and must not be in the past | Fix the input format, or ask for a current card. |

Two details that surprise people:

* **The checksum digit is not a gate.** A card number that fails the standard
  checksum is logged as a warning and processing continues — the authoritative
  answer comes from the issuing-range lookup. So do not expect a checksum
  rejection, and do keep your own client-side checksum check to catch typos
  early for the user's benefit.
* **`MM/YY` is accepted on input** by the collection SDK, but queries and
  notifications always return `MM/YYYY`. Store the four-digit form.

Related codes you may see at payment time rather than binding time: `A0029`
(card data invalid), `A0030` (card issuing range not found), `A0032` (payment
token data invalid), `A0023` (order cannot be created due to expired token).

## Why is the cardholder name rejected?

Because it contains characters outside the Latin alphabet. The rule is strict:
the cardholder name must contain **only English letters**. A name with accented
characters, or in a non-Latin script, is refused with the failure reason
`INVALID_CARDHOLDER_NAME` and the message *Cardholder name must contain only
English letters*, surfacing as `A0029` on the API.

How to handle it well:

* **Transliterate at the point of collection.** Ask for the name "as printed on
  the card"; cards are embossed in Latin characters, so the correct value almost
  always exists.
* **Validate in your own form** with a clear message, rather than letting the
  user discover it at payment.
* Remember the name is part of a card token's re-use key: the same card
  submitted with a differently spelled name is treated as a different
  credential and gets its own token.

## What does an MIT charge do differently from a CIT charge?

The request-level difference (`merchantInitiatedMode`, and the `A0045`
requirement for an already-usable credential) is on the
[card tokenization page](/docs/en/support/tokenization-faq). The **behavioural**
differences matter just as much:

| | CIT | MIT |
| - | - | - |
| Reaches the Waffo checkout page | Yes, when the flow needs it | **Never** — an MIT charge is blocked from the checkout page by design |
| 3DS | Can be influenced with `threeDsDecision` (`3DS_FORCE` / `NO_3DS`), subject to risk | `threeDsDecision` is **silently ignored** |
| User can pick another payment method after a failure | Yes, on the checkout page | No — there is no page and no user present |
| Tax collection that needs input from the user | Can be gathered on the checkout page | Not available in this path |

The single sentence to take away: **an MIT charge has no user interface and no
user**. Anything your flow relies on the checkout page for — a redirect, a
consent step, a field the user fills in — does not exist here.

## My MIT charge failed. Do I just retry it?

Not blindly, for three separate reasons:

1. **There is no user-side retry.** A CIT failure can end with the user choosing
   another method on the checkout page. An MIT failure just ends.
2. **Some failures are permanent for that credential.** `A0045` (the stored
   credential is not in a usable state) and `A0037` (the token is invalid or
   expired) will not become successful on a retry. The fix is a fresh
   customer-initiated authorisation.
3. **Retries are themselves rate-limited by card-scheme rules** — see the next
   question.

The pattern that works: classify the failure. Credential problems mean "ask the
user to re-authorise". Genuine transient failures mean "retry later, on a
schedule". Never retry in a tight loop, and never treat an MIT decline as
something the user can fix without being asked.

For subscriptions, do not build this yourself — Waffo's own retry schedule and
backup-payment-method handling already do it, as described in
[Subscription lifecycle & payment method management](/docs/en/support/subscription-lifecycle-management).

## Why was a card payment refused before it was even attempted?

Because card schemes impose **compliance limits on how many times the same card
payment may be retried**, and Waffo enforces them locally. When an attempt would
breach one, it is rejected by the system rather than being forwarded — which is
why the failure can come back faster than a normal decline, and why nothing
appears on the card statement.

What you need to know about the counting:

* It is counted **per card scheme**, and separately again according to the
  scheme's own classification of the merchant. Two cards from different schemes
  on the same order are not pooled.
* Waffo does not publish the thresholds, and you should not attempt to
  reverse-engineer them: they are set by the schemes and can change without
  anything changing on your account.
* The failure surfaces as the failure reason `CARD_PAYMENT_RETRY_LIMIT`, mapped
  to `C0005` on the API.

The correct handling is behavioural, not numeric:

* **Never auto-retry a declined card payment immediately.** It burns the
  allowance without any chance of a different answer.
* **Change something between attempts** — a different payment method, a
  re-entered card, a later schedule — or ask the user.
* If you are hitting this regularly, the problem is upstream: too many retries
  against cards that will not authorise.

## Can my users manage their saved payment methods themselves?

Yes, through one endpoint rather than through order creation:
`POST /api/v1/saved-payment-methods/config`, which takes your `merchantId`, the
`merchantUserId`, the saved-method token, and an `action`:

| Action | Effect |
| - | - |
| `SET_DEFAULT` | Makes that saved method the user's default. At most one default per user, so the previous default is replaced. |
| `SUSPEND` | Stops the saved method being offered to the user. |

Together with `/api/v1/tokenization/remove` (unbind entirely) that is enough to
build a complete "manage my payment methods" screen: list, set default, hide,
delete. What each of those does to the checkout ordering is on the
[card tokenization page](/docs/en/support/tokenization-faq).

Two boundaries worth knowing before you design the screen:

* The endpoint is in the **payin** scope of an API key, so it is a backend call —
  never expose it to a browser or a mobile client directly.
* Removal is final: a removed token is excluded from lookups and is never
  reissued. Suspension is the reversible-looking option; treat removal as
  permanent.

## Which of these belong on which page?

| Question | Page |
| - | - |
| What a token is, how to bind a card, session and token validity, re-binding the same card, default card | [Tokenization & Card Payments](/docs/en/support/tokenization-faq) |
| CIT vs MIT at request level, `A0045`, where binding results are notified | [Tokenization & Card Payments](/docs/en/support/tokenization-faq) |
| Token types beyond `CARD`, wallet-token fields and expiry, wallet re-authorisation | This page |
| Tokenization submit business failures, cardholder-name rule | This page |
| MIT behaviour, retry policy, scheme retry limits | This page |
| What each error code means in general | [Common Errors Troubleshooting](/docs/en/support/errors-troubleshooting-guide) |
| Which methods allow which refunds | [Payment Method Refund Limits](/docs/en/support/payment-method-refund-limits-faq) |

Still not sure why a specific binding or charge was refused? [Contact
support](mailto:support@waffo.com) with the `tokenRequestId` or the order ID —
the failure reason is recorded against both.


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