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

# Reading Your Payment Method Configuration

> How to discover programmatically which payment methods your account can use: every field on the pay-method configuration response, why currentStatus can come back empty, what the four refundAllowed codes mean, the parameter errors that name your merchant ID or your notifyUrl, and the export columns and list filters that changed.

This page is about the **API contract**: how to read your payment method
configuration from code, and the request-level errors that come back when a
configuration field is wrong.

Three neighbouring pages cover the same subject from other angles, and this page
does not repeat them:

* [Payin Agreements & Transaction Limits](/docs/en/support/payin-agreements-and-limits-faq) —
  the same configuration as the Portal renders it, on **Service → Payin**: every
  column, the fee notation, agreement statuses, filters and export.
* [Limits, Currency & Configuration Errors](/docs/en/support/error-codes-and-limits-supplement) —
  which limit each of `A0008` / `A0020` / `A0021` refers to, and how the daily
  ceiling is counted.
* [Conditional & Required Fields](/docs/en/support/conditional-required-fields-faq) —
  which order-creation fields become mandatory for which kind of payment method.

## How do I find out from code which payment methods my account can use?

Call `/api/v1/paymethodconfig/inquiry`. It takes one meaningful field:

| Field | Required | Notes |
| - | - | - |
| `merchantId` | Yes | Must be the same merchant ID the API key you signed with belongs to — see [the merchant-ID error below](#i-got-a-parameter-error-that-mentions-my-merchant-id-what-is-wrong) |

The response is your merchant ID plus `payMethodDetails`, an array with **one
entry per payment method contract that is currently in force on your account**.

Treat this endpoint as the source of truth for your own integration logic —
which methods to offer, which currencies to accept, what amount range to
validate against before you create an order — rather than hardcoding a list you
copied out of a specification once.

## What does each field in the response mean?

| Field | Meaning |
| - | - |
| `productName` | The payin product the contract belongs to: `ONE_TIME_PAYMENT`, `DIRECT_PAYMENT`, `SUBSCRIPTION` or `MINI_PROGRAM_PAYMENT` |
| `payMethodName` | The payment method's name, in the spelling you send back on order creation |
| `country` | The payment method's country, as a three-letter code. Two values are not countries: `EU_LIST` for a group of European markets, and `GLB` for a method not tied to a market |
| `payMethodType` | The category the method belongs to, for example `EWALLET`, `CREDITCARD`, `ONLINE_BANKING` — [the full enumeration](/docs/en/support/payment-method-coverage-faq#what-are-the-paymethodtype-categories) |
| `currency` | The currency this contract supports, as an ISO 4217 code |
| `orderAmountMin` / `orderAmountMax` | The order amount range this contract accepts, as decimal strings |
| `refundAllowed` | What shape of refund the method permits — [four codes, below](#what-do-the-four-refundallowed-values-mean) |
| `currentStatus` | Real-time availability: `1` available, `0` not available. **Can be empty** — see below |
| `fixedMaintenanceRules` | Scheduled unavailability windows, as pairs of cron expressions |
| `fixedMaintenanceTimezone` | The timezone the maintenance cron expressions are evaluated in, for example `UTC+08:00` |

Two parsing rules worth building in from the start:

* **Amounts are strings, not numbers.** Parse them as decimals; do not let a
  JSON library turn them into floats.
* **`orderAmountMin` / `orderAmountMax` can be absent.** Where a contract has no
  range configured, the field is simply not populated, and no amount-range check
  applies to that contract.

## Why does the same payment method appear more than once?

Because the array is keyed on **contracts**, not on payment methods. One entry
is emitted per contract in force, and a single payment method can legitimately
be under several: one per currency, or one per payin product.

So `payMethodName` is **not** a unique key in this response. If you are building
a lookup table, key it on the combination you actually need — typically
`payMethodName` + `currency`, or `productName` + `payMethodName` + `currency`.

Nothing in the response is de-duplicated for you.

## `currentStatus` came back empty. Is the payment method down?

No — an empty `currentStatus` means **"not known right now"**, not "unavailable".

The contract data and the real-time availability come from two different places.
The contract half is always present. The availability half is looked up
separately, and where that lookup does not cover a contract, `currentStatus`,
`fixedMaintenanceRules` and `fixedMaintenanceTimezone` are all left unset for
that entry rather than being guessed at.

How to handle it:

| `currentStatus` | What it means | Sensible behaviour |
| - | - | - |
| `1` | Available | Offer the method |
| `0` | Not available right now | Hide the method, or show it as temporarily unavailable |
| empty / absent | Availability could not be determined | **Do not treat as unavailable.** Fall back to your normal behaviour and let order creation be the authority |

The last row matters most: a client that reads "empty" as "off" will hide
perfectly working payment methods the moment an availability lookup has a bad
minute. The authoritative answer to "can this method take this payment" is
always the response to order creation.

## Does the response include contracts that have ended?

No. The query behind this endpoint selects only contracts that are in **`ACTIVE`
status** *and* whose validity window covers the current moment — a start date at
or before now, and either an end date at or after now or no end date at all.

Consequences:

* Contracts in `INIT`, `SUSPENDED`, `TERMINATED` or `ON_BOARDING` status do not
  appear.
* A contract that has not started yet, or whose end date has passed, does not
  appear.
* A payment method disappearing from the response is therefore a **contract**
  event, not an availability blip. A method that is contracted but temporarily
  unusable stays in the response with `currentStatus` of `0`.

So the two questions have two different answers in this response: *"am I
contracted for it?"* is whether the entry exists; *"can it take a payment right
now?"* is `currentStatus`. Do not use one to answer the other.

Agreement statuses as the Portal shows them are described in
[Payin Agreements & Transaction Limits](/docs/en/support/payin-agreements-and-limits-faq).

## What do the four `refundAllowed` values mean?

They describe the **shape** of refund the payment method permits — not whether
any particular order can be refunded right now.

| Code | Meaning | What a refund call gets |
| - | - | - |
| `0` | Refunds are not allowed on this payment method | Any refund is refused |
| `1` | One full refund only | A partial amount is refused; a second refund is refused |
| `21` | One refund, full or partial | A partial refund is accepted, but only once |
| `22` | Multiple refunds, full or partial | Accepted until the original amount is exhausted |

The Portal renders the same four values as *No*, *Only Full Refund*, *Full
Refund or One-time Partial Refund* and *Full Refund or Multiple Partial
Refunds*.

Two things this value does **not** tell you:

* **Whether the refund window is still open.** That is a separate setting; see
  [Payment Method Refunds & Limits](/docs/en/support/payment-method-refund-limits-faq).
* **Whether the order is in a refundable state.** An unpaid or already fully
  refunded order is refused regardless of `refundAllowed`.

## What are `fixedMaintenanceRules`, and how do I read them?

They are the payment method's **scheduled** unavailability windows: each rule is
a pair of cron expressions, `startRule` and `endRule`, marking the beginning and
the end of a window. `fixedMaintenanceTimezone` is the timezone they are
evaluated in — it is often not your own, so read it rather than assuming.

Typical shapes:

| Start / end pair | Window |
| - | - |
| `0 30 1 * * ?` / `0 30 2 * * ?` | Every day, 01:30 to 02:30 |
| `0 00 23 ? * 1,2,3,4,5` / `0 59 23 ? * 1,2,3,4,5` | Weekdays, 23:00 to 23:59 |
| `0 00 0 1,15 * ?` / `0 59 23 1,15 * ?` | All day on the 1st and the 15th of the month |

These are known in advance, so they are the one part of availability you can
usefully pre-empt in your UI. Unscheduled outages show up as `currentStatus` of
`0` instead, with no advance notice.

## How do I check the amount range before I create an order?

Read `orderAmountMin` and `orderAmountMax` off the matching entry — matching on
payment method **and currency**, since the range is per contract and two
currencies under the same method can have different ranges.

Validating client-side saves your customer a rejected order, but it is not the
only ceiling. There are three separate limits, with three separate error codes,
and this endpoint only exposes the first:

| Code | Which ceiling | Readable here? |
| - | - | - |
| `A0008` | The payment method's own amount range | Yes — `orderAmountMin` / `orderAmountMax` |
| `A0020` | A per-transaction limit on your account, per order currency | No |
| `A0021` | A daily cumulative limit on your account, per order currency | No |

They are independent: passing the payment method's range says nothing about the
account-level limits, and vice versa. The full breakdown, including how the
daily total is counted and when headroom comes back, is in
[Limits, Currency & Configuration Errors](/docs/en/support/error-codes-and-limits-supplement).

## I got a parameter error that mentions my merchant ID. What is wrong?

Every signed endpoint checks the `merchantId` in your **request body** against
the merchant ID that the **API key you signed with** belongs to. If they differ,
the request is refused before anything else happens, with the reason
`mid in params is inconsistent with apiKey`, returned under `A0003`.

This is deliberately a distinct message rather than a generic parameter error,
because the two causes are so different from every other parameter problem:

| Cause | What it looks like | Fix |
| - | - | - |
| Wrong credential for the environment | Sandbox key against a production merchant ID, or the reverse | Match key and merchant ID to one environment |
| Sub-merchant confusion | Sending a sub-merchant's ID in `merchantId` | `merchantId` is the ID the API key belongs to. Sub-merchants are identified in their own field — see [Multi-Brand & Sub-Merchants](/docs/en/support/multi-brand-and-sub-merchant-faq) |
| Copy-paste across accounts | Config from one account, key from another | Keep credentials and IDs in one place per account |

It never means the merchant ID does not exist, and it is never fixed by
retrying.

## `A0003` came back. How do I find out which field was wrong?

Read the message, not just the code. For `A0003` specifically, the specific
reason is **appended to the generic message in parentheses**:

```
Parameter validation failed ( refundAmount must be greater than 0 )
```

The part outside the parentheses is constant. The part inside names the actual
problem — usually the offending field and what was expected of it. `A0003`
comes back with HTTP `400`.

Two implications for your error handling:

* **Log the whole message, not the code.** A log line that records only `A0003`
  has thrown away the only part that identifies the bug.
* **Do not parse the parenthesised text programmatically.** It is written for a
  human reading a log. Branch on the code; show the text.

Codes other than `A0003` return their standard message with no appended detail,
unless the specific failure is one that is defined to carry extra context.

## What are the rules for my callback URL?

`notifyUrl` — the URL Waffo posts payment notifications to — is validated when
you configure it, and each failure mode has its own error:

| Rule | Failure |
| - | - |
| At most **512 characters** | `notify url exceeds maximum length of 512 characters` |
| Must begin with `http://` or `https://` | `invalid notify url format` |
| **ASCII characters only, no whitespace** anywhere in the URL | `invalid notify url format` |
| Must parse as a URL with both a scheme and a host | `invalid notify url format` |

Both are returned under `A0003`, with the specific reason in the message as
described above.

The two that catch people out are the last two. A URL with a non-ASCII character
in it — an internationalised domain name, or a path segment pasted from a
document — is refused even though a browser would open it: percent-encode it
first. And a trailing space picked up while copying is enough to fail the
whitespace check, with an error that looks like it is about the URL itself.

Leaving `notifyUrl` empty is allowed; no validation runs and no notifications
are sent to it.

## Do I have to send `subscriptionManagementUrl`, and where does it end up?

It is **documented as required** when you create a subscription, and is capped
at **256 characters** — but note that the create call today only enforces the
length: a request without it is **not rejected**. Treat it as required anyway,
because on one account setting (below) it is the only management link your
customer will ever get. What it is for, and why it should be a page that opens in
a desktop browser rather than an app deeplink, is covered in
[Subscription FAQ](/docs/en/support/subscription-faq).

The part that page does not cover is **which management link your customer
actually receives**. That is an account-level setting with two values:

| Setting | The management link in subscription emails is |
| - | - |
| `INTERNAL` | A Waffo-hosted management page, generated per email |
| `MERCHANT_API_URL` | Exactly the `subscriptionManagementUrl` you sent on create |

Two consequences:

* **On `MERCHANT_API_URL`, your value is the link.** If you did not send one, or
  sent a page that has since moved, the link in your customer's email is broken
  and nothing else compensates for it. Read it back on subscription inquiry to
  confirm what is stored.
* **On `INTERNAL`, links from older emails stop working.** Each new email
  generates a fresh link and invalidates the previous ones, so a customer
  digging out last month's email will need the latest one instead. The generated
  link is also time-limited in its own right.

The management page is not part of the real-time payment flow either way — it is
somewhere you point the customer *after* the subscription payment succeeds. See
[Subscription Notifications](/docs/en/support/subscription-notifications-faq) for
which events send an email at all.

## Which columns changed in my transaction and refund exports?

Three changes, and one of them is easy to misread:

| Column | Where it is | What it carries |
| - | - | - |
| `Initiation Type` | Transaction export | `CIT` or `MIT` — who triggered a card transaction |
| `Pay Method Country/Region` | Transaction export | The payment method's country. **Replaces the former `Country/Region` column** |
| `Merchant Original Order ID` | **Refund export only** | On a refund row, your own order ID for the original payment |

The trap is the third row. `Merchant Original Order ID` is defined on the shared
column set but is **deliberately excluded from the transaction export** — it
only means something on a refund row, where it distinguishes your ID for the
refund from your ID for the payment it reverses. If your transaction export
parser is looking for it, it will not find it.

On `Initiation Type`: it is frequently empty. Why, and what populates it, is in
[Transaction & Refund Field Reference](/docs/en/support/transaction-field-reference-faq).

Exports are queued background tasks rather than instant downloads — see
[Exports & Report Downloads](/docs/en/support/export-center-faq).

## Can I narrow the chargeback or refund lists to one subscription or one order?

Yes, and these are the filters people most often miss:

| List | Filter | Matching |
| - | - | - |
| Chargebacks | `relatedSubscriptionId` | All chargebacks arising from one subscription |
| Refunds | `relatedSubscriptionId` | All refunds arising from one subscription |
| Refunds | `merchantOriginalOrderId` | **Exact match** on your own order ID for the original payment |
| Transactions | `relatedSubscriptionId` | All payments belonging to one subscription |

`merchantOriginalOrderId` is the one to reach for when a customer disputes a
purchase and you know only your own order reference: it goes straight from your
order ID to every refund against that payment, without having to look up Waffo's
identifiers first.

Note the naming. The filter is `relatedSubscriptionId`, not `subscriptionId` —
"related" because the row being filtered is a chargeback, refund or payment that
*came from* a subscription, not the subscription itself. To list subscriptions,
filter a subscription list on `acqSubscriptionId` or
`merchantSubscriptionId` instead.

## A customer reopened an old checkout link. What do they see?

A checkout link is a **session**, and sessions do not last forever. Two separate
lifetimes apply:

* Before payment, the link is valid for a limited period from creation. Opened
  after that, it reports the checkout as expired rather than taking a payment.
* After the order reaches a final state, the link stops being a payment page and
  becomes a **result** page, viewable for a period after completion, then no
  longer.

Neither lifetime is something to build your customer experience on. If a
customer needs to see a completed purchase, show it from your own records, or
send them a receipt — see
[Receipt Configuration & Download](/docs/en/support/receipt-configuration-and-download-faq).
And if they need to pay, create a fresh order rather than resending an old link;
what happens to an order behind a reopened link is covered in
[Checkout Order Lifecycle](/docs/en/support/cashier-order-lifecycle-faq).

## Where do I look next?

| Question | Page |
| - | - |
| The same configuration in the Portal, column by column | [Payin Agreements & Transaction Limits](/docs/en/support/payin-agreements-and-limits-faq) |
| What Waffo catalogues at all, and the full field enumerations | [Which Payment Methods and Markets Can I Accept?](/docs/en/support/payment-method-coverage-faq) |
| Which limit each error code refers to | [Limits, Currency & Configuration Errors](/docs/en/support/error-codes-and-limits-supplement) |
| Which order fields a given payment method needs | [Conditional & Required Fields](/docs/en/support/conditional-required-fields-faq) |
| Refund windows and per-method refund behaviour | [Payment Method Refunds & Limits](/docs/en/support/payment-method-refund-limits-faq) |
| Signing requests, and what the signed merchant ID is | [Authentication & Signatures](/docs/en/support/auth-signature-faq) |
| Notification delivery, retries and resends | [Webhook Events & Resends](/docs/en/support/webhook-events-and-resend-faq) |
| Sub-merchant identification | [Multi-Brand & Sub-Merchants](/docs/en/support/multi-brand-and-sub-merchant-faq) |


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