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

# Conditionally Required Fields by Payment Method

> Why fields marked Optional on /api/v1/order/create — goodsId, goodsUniquePrice, skuName, userFirstName, userPhone, address and friends — can still be rejected as missing, which field paths can appear in a missing-field list, and why the correct pattern is to submit and read the list back rather than predict it.

The acquiring specification marks `goodsInfo`, `addressInfo` and several `userInfo`
fields as optional, and the backing objects really do carry length limits only — no
required constraint. Yet a request that passes schema validation can still come back as
`A0003 Parameter validation failed` naming one of those fields. `payMethodType` values
used below are listed in the
[data reference](/docs/en/developer-docs/tools-and-references/references/data-reference).

## Why is a field marked Optional rejected as missing?

Because "required" is not a property of the API — it is a property of the **payment
method actually used** plus the **scenario** you are in. One integration sends card
payments and needs nothing from `goodsInfo`; another sends an installment payment and
needs the full itemised basket. A single schema cannot mark both. Two consequences:

* **Requirements are not a stable function of the payment method alone.** The same
  method can ask for different fields depending on how the payment is processed, and the
  set can change without a change on your side, so a per-method list hard-coded in your
  integration will drift out of date silently.
* **There is no pre-flight discovery endpoint.** `/api/v1/paymethodconfig/inquiry`
  returns availability, maintenance windows, amount range, currency and refund policy —
  it does **not** return field requirements.

## What is the correct pattern, then?

Submit, read the list, resend. Do not predict.

1. Send the request with everything you already hold.
2. If required fields are missing, the call fails with `A0003`. For `A0003` specifically
   the response `msg` carries the field-level detail appended in parentheses; on
   `/api/v1/order/refund` the missing paths also come back as a `missingFields` list.
3. Fill in exactly those paths and resend.

Paths are returned in dotted form — `userInfo.userFirstName`, `addressInfo.postcode`,
`goodsInfo.goodsUniquePrice` — so they map straight onto your request body.

**If you use the hosted cashier you usually never see this.** When the chosen method
needs fields you did not supply, Waffo routes the order to the cashier and the cashier
renders those inputs for the buyer. The loop above matters most where there is no
cashier to fall back on: merchant-initiated transactions (`merchantInitiatedMode`) and
agent payments settled directly.

## Which field paths can appear in a missing-field list?

| Object | Paths that can be requested |
| - | - |
| `userInfo` | `userFirstName`, `userLastName`, `userPhone`, `userEmail`, `userId`, `userIDNumber` |
| `userInfo.userBankInfo` | `bankAccountNo`, `bankCode`, `bankName`, `bankBranch` (refunds paid out by transfer) |
| `addressInfo` | `address`, `city`, `region`, `postcode`, `addressCountryCode` |
| `goodsInfo` | `goodsId`, `goodsName`, `goodsUniquePrice`, `goodsQuantity`, `skuName`, `goodsCategory`, `goodsUrl` |
| `paymentInfo` | `payMethodUserAccountType` (`EMAIL` / `PHONE_NO` / `ACCOUNT_ID`) with `payMethodUserAccountNo` |
| `payMethodProperties` | market-specific identifiers — `cpf`, `curp`, `nin`, `cc`, `ci`, `bankCode`, `accountNumber`, `receiveBank`, `cryptoChain`, `cardToken`, `paymentToken` |
| `extendInfo` | `refundAddress`, as a key inside the `extendInfo` JSON string |

## Roughly which method types ask for what?

Directional only — useful for sizing your data collection, **not** a contract. Let the
returned list decide. Requirements also vary by market for the same method type, which
is the other reason a static list does not hold.

| `payMethodType` | Commonly requested |
| - | - |
| `BNPL` | The largest set: the itemised basket (`goodsId`, `goodsName`, `goodsUniquePrice`, `goodsQuantity`, `skuName`, `goodsCategory`, `goodsUrl`) plus buyer name, phone, email and a full shipping address |
| `EWALLET` | Often nothing extra; some ask for buyer phone, some for the wallet account pair (`payMethodUserAccountType` + `payMethodUserAccountNo`) |
| `ONLINE_BANKING`, `DIGITAL_BANKING` | Buyer name and contact details; **refunds** on these rails commonly need bank account details |
| `VA`, `OTC` | A small set — typically buyer first and last name |
| `CREDITCARD`, `DEBITCARD` | Nothing from `goodsInfo`; in some markets a national identifier via `payMethodProperties` |

## Which of these are actually unconditional?

Do not spend effort on fields that are simply mandatory. On `/api/v1/order/create`,
`userInfo.userId`, `userInfo.userTerminal` and `orderDescription` are required for every
request. `userInfo.userEmail` is required in practice: with no real address collected,
send a unique fallback in the form `userId@examples.com` — never a shared or placeholder
value, which can trip risk controls. Inside `goodsInfo`, the contract expects **either**
`goodsUrl` **or** `appName` (not both) for compliance and risk review; the create call
does not reject their absence on its own — only a payment method's conditional rule
does, through the missing-field list above.

## Requirements driven by the scenario, not the method

Triggered by *how* you integrate, and checked before any method-specific rule runs:

| Scenario | Additionally required |
| - | - |
| Card data or `paymentTokenData` passed directly in the API | `userInfo.userBrowserIp` and `userInfo.userAgent`, plus the card fields — number, expiry month and year, CVV, cardholder name |
| `merchantInitiatedMode` set | `userPaymentAccessToken` |
| Agent payment in hosted mode | `successRedirectUrl` |
| Refunding a stablecoin order | `refundAddress` inside `extendInfo` |

## Do refunds use the same list?

No. Refunds run their own check and can ask for identity and payout details a payment
never needs, because the money often goes back by transfer rather than as a reversal on
the original payment — which is why the refund request accepts `userInfo` (including
`userBankInfo`), `addressInfo` and `extendInfo`. Same submit-and-read loop.

## A field is present but still rejected. Why?

Format rules apply to every field you send, whether or not it was requested:

| Field | Rule |
| - | - |
| Any string field | An empty string is normalised to absent — sending `""` is the same as omitting it, and will not satisfy a missing-field requirement |
| `userPhone` | `+CountryCode-Number`, e.g. `+62-08990013157` — must start with `+` and contain `-` |
| `userEmail` | Must be a valid email address — an invalid one is the one format failure that is **not** `A0003`: it comes back as `B0007 Invalid email format` |
| `goodsQuantity` | An integer, or a numeric string; anything else is rejected at parse time |
| `userBirthDay`, `userIDIssueDate`, `userIDExpiryDate` | `dd/MM/yyyy` |
| `addressCountryCode`, `nationality` | A recognised ISO country code |
| `userIDType` | Currently `ID` |

Length limits are separate, are listed per field in the acquiring specification, and
also produce `A0003` with the field named.

***

Still blocked after resending exactly what the response asked for?
[Contact support](mailto:support@waffo.com) with the `paymentRequestId` and the full
`A0003` message.


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