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

# Batch Refunds

> The Portal's Batch Refunds tab: template columns and limits, what upload validation checks and in which order, duplicate-file and one-batch-at-a-time rules, batch statuses, and where per-row failure reasons live.

Batch Refunds is a tab of the Portal's **Transactions** module, next to Payments, Refunds and Reports, and the only
place in the Portal where refunds are *initiated*. Each row of your file becomes an ordinary refund record, so
preconditions, partial refunds, cross-currency amounts and returned fees behave exactly as described in [Refund
FAQ](/docs/en/support/refund-faq).

The tab is enabled per account — if it is not in your Portal, it has not been switched on for you. Inside it,
**Export Batch Refund Template** and **Upload Batch Refund File** require the batch-refund upload permission,
while the list, its filters and **Export** are open to any role that can open the tab: if you can see the
buttons, you can use them.

## What does the flow look like end to end?

| Step | Portal action | What happens |
| - | - | - |
| 1. Download template | **Export Batch Refund Template** | 4-column CSV with one sample row, `Batch_Refund_Template.csv` |
| 2. Upload | **Upload Batch Refund File** | The file is validated as a whole; nothing is created yet |
| 3. Preview | Confirm dialog | Shows record count, total amount, currency |
| 4. Confirm | **Confirm** in the dialog | Rows are re-validated, then batch and refunds are created |
| 5. Track | Batch list filters and **Export** | Filter and export the batch list |

The whole flow is a Portal feature performed in your browser session; there is no batch endpoint on the
API-key-signed integration API — for an integration, use the single refund API instead. Confirming produces a
**Refund Batch ID** shaped `BRP` + MID + `YYYYMMDD` + a 6-digit daily sequence, e.g.
`BRP<your merchant ID>20260225000001`; the batch's **Batch Name** is the file name you uploaded, so name files
something you will recognise later.

## What must the file look like?

| Template column | Required | Notes |
| - | - | - |
| `Original Order Id` | Yes | Waffo acquiring order id of the payment being refunded |
| `Refund Amount` | Yes | Greater than zero, within the currency's decimal precision |
| `Refund Currency` | Yes | 3-letter code, identical for every row, equal to the order's currency |
| `Refund Reason` | No | Free text, carried onto the refund record |

Hard limits: `.csv` only, at most **2 MB**, at most **2,000 records**, non-empty and parseable — the Portal checks
extension and size before uploading, the server checks both again. Two things catch people out: **delete the
sample row** (a row carrying the template's example order id is rejected on its own, before any other check), and
**one currency per file**.

## Why was my whole file rejected?

Upload validation is all-or-nothing: nothing is created, and the Portal lists offending rows as *row, field,
message*. Checks run in a fixed order, which is why you often see only one category at a time:

1. File-level checks — format, size, emptiness, duplicate content, a batch already in progress.
2. The sample-row check; if it fires, it is the only error returned.
3. Per-row checks — order exists under your MID, no duplicate order inside the file, amount > 0, currency valid,
   consistent, and equal to the order's currency.
4. The single-refund preconditions per row — order paid and refundable, no refund already in progress, inside the
   refund window, amount within the remaining refundable amount, no dispute lock ([Refund FAQ](/docs/en/support/refund-faq) explains each).
5. The batch balance check, **only if every row passed** — the file's total must be covered by the same refund
   funding source a single refund would use, or you get one batch-level error with no row number ("Insufficient
   merchant refund account capacity for batch refund").

Stablecoin orders (USDT, USDC) cannot go in a batch: they require a refund address, for which the template has no
column. Refund those individually.

## Why can't I upload — duplicate file, or a batch in progress?

Duplicate detection works on file **content**, not name, scoped to your MID. Re-uploading the same content while
an earlier upload is still awaiting confirmation returns that same pending upload, so you can re-open the confirm
dialog; once a batch has been created from that content, the content is rejected permanently — to retry failed
rows build a new file, and any change, even the reason text, makes it a new file. A validated upload also expires
if left sitting: confirming later fails with *File token is invalid or expired*, and you upload again.

Separately, upload and confirm both refuse while **any refund in any of your batches is still in progress** ("You
have a batch refund in progress. Please wait for it to complete."), so plan large runs as a queue of files; single
refunds via the API are not blocked by this. And because confirm re-validates every row, a batch left sitting in
the confirm dialog while things changed — an order refunded elsewhere, a window closed, the balance moved — fails
at confirm with the first offending row's message instead of creating a partly invalid batch.

## What do the batch statuses mean?

| Status | Meaning |
| - | - |
| `INIT` | Transient — set as the batch record is created, recalculated immediately. Rarely visible, and not offered in the status filter |
| `PROCESSING` | At least one row is still pending or in progress |
| `PARTIAL_SUCCESS` | All rows finished; some succeeded, some failed |
| `FULL_SUCCESS` | Every row succeeded |
| `FAILED` | Every row failed |

Status is derived from the row counts rather than tracked separately, so it moves on its own as rows settle.
**Total / Success / Failed / Processing Count** are shown per batch: Failed Count includes rows that failed
validation at submit time, Processing Count rows not yet started. One bad row does not stop the batch — rows are
processed independently, which is what `PARTIAL_SUCCESS` means, and a batch has no retry action: put the failed
rows in a new file, or refund them one at a time.

## Where do I see which rows failed and why?

Click **Detail** on a batch: it opens the **Refunds** tab pre-filtered by that Refund Batch ID, carrying the batch
page's date range. Each refund is then a normal record — its detail page shows the Refund Batch ID, and the
failure reason when the status is *Refund Failed*. The Refunds tab also has a Refund Batch ID column and filter,
so you can reach the same view directly.

The batch list export (`Batch_Refunds_<timestamp>.csv`) is a **batch-level summary** — ids, name, amount, currency,
status, the four counts, operator, created and updated times — and does **not** contain per-row failure reasons;
times in it use the UTC offset your browser reports. The list itself filters on Refund Batch ID, status, currency
and created datetime, and opens on the last three months. Rarely a row fails so early that no refund record is
created: it still counts in Failed Count with nothing to open in the Refunds tab — contact support with the
Refund Batch ID for those.

## How does this relate to the single refund API?

Same engine, different entry point: identical lifecycle, account selection and settlement treatment. Two practical
differences. **Refund request id** — batch rows get a system-generated one you never see, so track them by Refund
Batch ID or by the refund order id in the Portal, not by your own reference. **Notifications** — batch rows carry
no per-row notify URL, so refund notifications go to the default refund notification URL configured for your
account; with none configured, batch rows send no webhook and the Portal is your only view.

***

A file that keeps failing? [Contact support](mailto:support@waffo.com) with it and, if one was created, the Refund Batch ID.


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