Skip to main content
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:

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

Call /api/v1/paymethodconfig/inquiry. It takes one meaningful field: 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?

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

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. 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.
  • 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: 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: 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.

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: 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:
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: 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. The part that page does not cover is which management link your customer actually receives. That is an account-level setting with two values: 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 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: 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. Exports are queued background tasks rather than instant downloads — see Exports & Report Downloads.

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

Yes, and these are the filters people most often miss: 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 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. 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.

Where do I look next?