---
url: /refunds.md
---
# Refunds

Return a captured payment through the Maytes merchant portal or merchant API. Refunds settle asynchronously, so keep your order record in sync with the checkout status.

::: info Partial refunds are coming
Today a refund returns the **full captured amount**. Support for partial refunds is on its way.
:::

Requirements:

* Only a **`captured`** checkout can be refunded. For an order that's merely *authorized*, [cancel](/capture-and-status#if-you-can-t-fulfill-the-order) it instead — no money has moved yet.
* The customer always gets **100% back** on the same card they used to pay.
* Refunds settle **asynchronously** with the payment provider — the checkout passes through a brief `refunding` state before landing on `refunded`.
* API requests require a unique **`merchant_refund_ref`**. Reusing the same value safely returns the existing refund.

***

## Issue a refund

In the portal, find the captured checkout and choose **Refund**.

To refund through the API, call [`POST /api/merchant/v1/checkouts/{checkout_uuid}/refund`](/api-reference#api-1/tag/checkout-merchant/POST/api/merchant/v1/checkouts/{checkoutUuid}/refund) with a unique reference:

```bash
curl -sX POST https://api.maytes.co/api/merchant/v1/checkouts/$CHECKOUT_UUID/refund \
  -H "authorization: Bearer $ACCESS_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "merchant_refund_ref": "refund_order_1001",
    "reason": "Customer requested a refund"
  }'
```

The endpoint returns `204` when the refund is accepted. Use `RefundCheckout` in the [backend SDK](/build-your-integration) for the same operation. Then read the checkout until it reaches `refunded`.

***

## What your integration sees

After a refund is issued, the checkout moves from `captured` → `refunding` → `refunded`. The [GET checkout](/capture-and-status#reading-the-checkout-status) response includes these refund fields:

```json
{
  "data": {
    "checkout_uuid": "019fbb9e-ecd1-7059-99f3-f8bf47b63931",
    "status": "refunded",
    "amount": 4500,
    "currency": "AUD",
    "refunded_amount": 4500,
    "merchant_processor_fee": 0,
    "merchant_service_fee": 0,
    "last_refund_at": "2026-09-14T10:14:33.000Z"
  }
}
```

| Field | What it means |
|---|---|
| `status` | `refunding` while settling, `refunded` once the payment provider confirms. |
| `refunded_amount` | The amount returned to the customer (minor units). `0` while pending; matches the captured amount once settled. |
| `last_refund_at` | ISO timestamp of the confirmed refund. `null` while pending. |
| `merchant_processor_fee` | Reserved for processor cost billed to you on the refund. **Currently always `0`** — refund fee billing is not yet applied. |
| `merchant_service_fee` | Reserved for the Maytes service fee billed to you on the refund. **Currently always `0`.** |

::: tip Customers always get 100% back
If refund fee billing is introduced, `merchant_processor_fee` and `merchant_service_fee` will be **billed to you**. They will never be deducted from the customer's refund.
:::

## Keeping your records in sync

Treat `refunded` as terminal: the order is unpaid again and the customer has their money. Keep your records in sync in either of these ways:

1. **When reading a checkout** — in support tools and reconciliation jobs, branch on `status` and handle `refunding` and `refunded` with the other [lifecycle states](/capture-and-status#the-checkout-lifecycle).
2. **When issuing the refund** — update your order record when the API accepts the request or your team submits it in the portal. If you need confirmation, poll `GET /checkouts/{uuid}` until `status` is `refunded`.

There is no refund webhook event. Because you initiate the refund, use the checkout status to confirm completion.

## What's next

* [Capture & checkout status](/capture-and-status) — understand the lifecycle and cancel before capture.
* [API reference](/api-reference) — refund request and checkout response schemas.
