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

# Refund FAQ

> Answers to the most common questions about Waffo refunds: multiple partial refunds, refund preconditions, idempotent retries, cross-currency amounts and rounding, refund vs cancel, processing time and payment method limitations.

This page answers the questions merchants ask most often about refunds.
Field-level contracts live in the Acquiring API Specification
(`/api/v1/order/refund`, `/api/v1/refund/inquiry`, Refund order notification).

## Can I refund the same order more than once?

It depends on the payment method and your contract configuration:

* **Multiple partial refunds supported** — each refund is validated so that
  *refunded + in-progress + current request ≤ original payment amount*. A
  single order allows up to **50 refunds** in production; exceeding this returns
  error `A0027`. Sandbox and test environments use a lower cap, so hitting
  `A0027` there after only a few refunds is expected, not a bug. Every refund response includes `remainingRefundAmount` so you can
  track what is still refundable.
* **Not supported** — once one refund has completed for the order, any
  further refund request is rejected.

Each successful partial refund sets the order's **refund status**
(`refundStatus`) to `ORDER_PARTIALLY_REFUNDED`; it only becomes
`ORDER_FULLY_REFUNDED` when the total successfully refunded amount equals the
order amount. The order's `orderStatus` itself is **not** changed by a refund —
a fully refunded order still reports `PAY_SUCCESS`.

## Why was my refund rejected?

A refund request must pass all of the following preconditions:

| Precondition | Detail |
| - | - |
| Order status | The original payment must be successful. |
| Contract supports refunds | Refund capability is a contract setting; requests against a non-refundable contract fail (e.g. `A0014`). |
| Refund validity window | Refunds are only accepted within a contract-defined period counted from the **order's creation date** (not from payment success), ending at the end of the last day. Check `refundExpiryAt` in the order inquiry response — it is `null` when refunds are not allowed. |
| Sufficient funds | Waffo first draws the refund from your pending-settlement balance (available balance including any overdraft allowance). If that is not enough it falls back to your refund reserve account. Only when neither can cover the amount is the request rejected with `A0012`. |
| No open dispute | An order under chargeback dispute is locked against refunds (`A0019`). An order already refunded by the chargeback alert service returns `A0039`. |

Validity windows and multi-refund support are
contract-dependent — check your contract in the Merchant Portal or contact
integration support for your exact configuration.

## My refund request timed out. Can I safely resend it?

Yes. `refundRequestId` is the idempotency key:

* Resending with the **same ID and same amount** returns the current refund
  status; if the refund is still in an intermediate state, processing is
  pushed forward.
* Resending with the **same ID but a different amount** is rejected.

To retry safely, always resend the identical request. Use a new
`refundRequestId` only when you intend to create an additional refund.

## How are cross-currency refund amounts calculated?

When the user paid in a currency different from your order currency, the
refund amount you submit (in order currency) is converted to the user's
currency. Two rules apply:

* **Round down** — converted refund amounts are always rounded down, so the
  total refunded never exceeds what the user actually paid.
* **Last refund tops up** — with multiple partial refunds, FX rounding could
  leave the refunded total slightly below the payment amount. The final
  refund that exhausts the remaining balance returns the full remainder, so
  a fully refunded order truly reaches `ORDER_FULLY_REFUNDED`.

The `finalDealAmount` in the refund inquiry response is the actual amount
the user receives in their currency.

## I cancelled the order, but the user says they already paid

Cancellation only works on orders still in an intermediate state. If the
user has in fact completed the payment, the cancel request fails and the
order proceeds as a successful payment — you receive a payment success
notification. To return the funds at that point, use the refund API; do not
retry the cancel.

## How long does a refund take? The status stays in progress

Refunds are **asynchronous**. A successful `/api/v1/order/refund` call means
the request was *accepted* (`REFUND_IN_PROGRESS`), not completed. Waffo then
processes the refund with the payment method and automatically retries
recoverable failures until a final state is reached.

* Provide `refundNotifyUrl` to receive the final result via the refund
  order notification, or poll `/api/v1/refund/inquiry`.
* A refund is final only at `ORDER_PARTIALLY_REFUNDED`,
  `ORDER_FULLY_REFUNDED` or `ORDER_REFUND_FAILED` — treat
  `REFUND_IN_PROGRESS` as pending, not failed.

## Do all payment methods support API refunds?

No, and there are two distinct cases.

**Refunds not permitted.** Two codes, two meanings. `A0017` ("payment channel
not supported") means the payment method itself cannot carry a refund — full or
partial. `A0014` ("refund rules") means the method can refund but your
agreement's refund shape does not allow *this* request: a partial refund on a
full-refund-only row, or a second refund where only one is permitted. Asking for
more than the remaining refundable amount is a third thing again — a plain
`A0003` parameter failure. See
[Refund Limits by Payment Method](/docs/en/support/payment-method-refund-limits-faq).

**Refunds needing extra fields.** Some refunds require additional user
details, such as bank account information, because the money is returned by
transfer rather than reversed on the original payment. **Which fields are
required is configured per channel, so do not decide it from the payment
method in your own code.** Submit the refund; if fields are missing the API
returns `A0003` together with the list of missing field names, and you resend
with those fields filled in.

For some payment methods refunds are executed over payout rails on Waffo's
side — your API contract stays the same.

## Can I submit many refunds at once?

Yes — the **Batch Refunds** page under the Portal's Transaction module. It is
enabled per merchant account, so if the page is not there it has not been
switched on for you; ask support. The template and upload buttons are visible
only to the roles that can already initiate a single refund.

The flow is: export the batch refund template (CSV), fill it in, upload it,
confirm the summary dialog (record count, total amount, currency), and Waffo
creates a **Refund Batch ID** in the form `BRP` + MID + `YYYYMMDD` + a 6-digit
sequence — for example `BRP<your merchant ID>20260225000001`.

| Template column | Required |
| - | - |
| Original Order Id | Yes |
| Refund Amount | Yes |
| Refund Currency | Yes |
| Refund Reason | No |

## My whole batch file was rejected. Why?

Upload validation is all-or-nothing: a single bad row rejects the whole file,
and you correct it and upload again. What is checked:

* **The file** — must be the exported template in CSV, with exactly its four
  columns and no extra ones; not corrupted, encrypted or empty; at most
  **2,000 records** and **2 MB**; and not a file already uploaded before
  (duplicates are detected from the file content, not the name).
* **Currency** — one refund currency for the entire file, and it must equal
  each original order's currency.
* **Each row** — the original order exists, is paid and not yet fully
  refunded, its payment method allows refunds, it is still inside the refund
  window, the amount is above zero and no more than the remaining refundable
  amount, and the order has no refund already in progress.
* **Duplicates** — the same order may not appear twice in one batch.
* **Batch total** — the file's total must be covered by your
  pending-settlement balance plus your refund reserve balance at upload time.

The per-row rules are the same preconditions a single refund passes; see
"Why was my refund rejected?" above for what each one means.

## How do I track a batch after submitting it?

Rows are processed independently: a failing row is marked with its reason and
the rest continue — one failure does not stop the batch.

| Batch status | Meaning |
| - | - |
| `INIT` | Batch created, processing not started |
| `PROCESSING` | At least one row still in progress |
| `PARTIAL_SUCCESS` | Some rows succeeded, some failed |
| `FULL_SUCCESS` | Every row succeeded |
| `FAILED` | Every row failed |

The batch list shows total / success / failed / in-progress counts, the batch
amount and currency, and the creation and update times. Every refund carries
its Refund Batch ID, so you can filter the refund list by batch, open any
individual refund's detail as usual, and export the batch result including
failure reasons. There is no retry action on a batch — resubmit the failed
rows in a new file, or refund them one at a time.

## Do I get the original payment fee back on a partial refund?

The default is **refund without fee return**. If your contract includes a
returned fee, a refund returns the matching share of the original payment fee,
rounded **down** to 2 decimals:

```
Returned fee = ROUNDDOWN2( refund amount / original order amount × original fee amount )
```

A partial refund returns the pro-rata share, a full refund returns all of it.
Because the rule rounds down, the returned fee can land a cent below the figure
you get from straight pro-rata arithmetic. It appears as a positive component
of the refund row's Settlement Amount — see
[Settlement Reports & Exports](/docs/en/support/settlement-reports-faq).


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