# Refunds

Refund a succeeded payment session with
[`POST /gateway/sessions/{id}/refund/`](/reference/operations/refundpaymentsession/).
Refunds can be **full** or **partial**, and a session can be refunded several times
until its whole amount has been returned.

## Request

```bash
curl https://api.paymee.online/api/v1/gateway/sessions/ps_0b7c3a1e-…/refund/ \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c0e9a-2d4b-4b8e-9a51-0c3f7d2e8b14" \
  -d '{
    "reason": "Damaged item",
    "refund_type": "PARTIAL",
    "amount": "3.000"
  }'
```

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `reason` | string (≤ 500) | ✅ | Why the refund is issued. |
| `refund_type` | string | – | `FULL` (default): the whole amount of the payment. `PARTIAL`: the `amount` you give. |
| `amount` | string | for `PARTIAL` | A positive decimal string in TND, e.g. `"3.000"`. Extra decimals are rounded to the millime. |
| `metadata` | object | – | Your own key/values, stored with the refund. |

A `FULL` refund always means the full amount of the payment. After a partial refund,
return the rest with a `PARTIAL` refund of the remaining amount.

### Retrying safely: `Idempotency-Key`

A refund request that times out may or may not have been processed. To retry without
refunding twice, send an `Idempotency-Key` header: a unique value you generate per
refund (a UUID v4 is ideal), up to 255 characters.

- Repeating a request with the **same key and the same body** returns the refund that
  key created — with the `Idempotent-Replayed: true` header — instead of refunding again.
- If that refund had been refused (for example `insufficient-funds`), repeating it with
  the same key tries again.
- Using a key that was already used for a **different** refund request (another body or
  another session) returns `409` `idempotency-key-reuse`.

Keys do not expire. Without the header, every request is a new refund: a retried
`PARTIAL` request would refund twice. (Retrying a `FULL` refund without a key is refused
as `over-refund`, so it is safe either way.)

## The refund object

A refund request answers `200 OK` with the refund:

```json
{
  "id": "re_4f1d0c2e-9b7a-4c51-8f0e-2a6b1c3d4e5f",
  "session_id": "ps_0b7c3a1e-5d2f-4e8a-9c6b-1f2e3d4c5b6a",
  "merchant_reference": "order-0001",
  "amount": "3.000",
  "currency": "TND",
  "refund_type": "PARTIAL",
  "status": "SUCCEEDED",
  "reason": "Damaged item",
  "processor_message": null,
  "created_at": "2026-11-21T09:12:01.870000Z",
  "completed_at": "2026-11-21T09:12:03.095000Z"
}
```

| `status` | Meaning |
| --- | --- |
| `SUCCEEDED` | The money is on its way back to the buyer's card. `completed_at` is set. |
| `PENDING` | The refund is accepted but could not be confirmed automatically; it is completed manually and can take a few days. `processor_message` says why. We contact you if anything is needed on your side. |
| `FAILED` | The money was not returned. `completed_at` is set and `processor_message` says why. |

Most card refunds are `SUCCEEDED` straight away. A `PENDING` refund ends as `SUCCEEDED`
or `FAILED`, and you are told by webhook: **`refund.succeeded`** or **`refund.failed`**
(see [Webhooks](/guides/webhooks/#refund-events)). These events are sent for every refund,
including those issued from your dashboard.

Every refund of a session is also listed, oldest first, in the `refunds` array of
[`GET /gateway/sessions/{id}/`](/reference/operations/getpaymentsession/) — use it for
reconciliation. A refund does not change the session's own `status`, which stays
`SUCCEEDED`.

`processor_message` is informational, for your logs and support; don't branch on its
text.

## Rules

- Only a `SUCCEEDED` session can be refunded. Refunds are always in TND.
- The total of a session's refunds can never exceed what was paid. A refund over the
  remaining amount is refused (`over-refund`).
- A partial refund can be refused until the payment has settled, normally the day after
  the payment (`partial-requires-settled`). A full refund is accepted right away.
- A refund can be refused when it can't be funded from your balance
  (`insufficient-funds`).

## Errors

Errors are [problem documents](/guides/errors/). A refusal changes nothing: you can fix
the cause and send the request again.

| `type` suffix | HTTP | When |
| --- | --- | --- |
| `bad-request` | 400 | The body isn't valid JSON, or the `Idempotency-Key` is empty or over 255 characters. |
| `not-found` | 404 | No such session on this account. |
| `idempotency-key-reuse` | 409 | The `Idempotency-Key` was already used for a different refund request. |
| `validation-error` | 422 | `reason` missing, unknown `refund_type`, or a `PARTIAL` refund without a positive `amount`. |
| `invalid-state` | 422 | The session or its payment can't be refunded (e.g. the session isn't `SUCCEEDED`). |
| `no-payment` | 422 | The session has no successful payment. |
| `partial-requires-settled` | 422 | Partial refund before the payment has settled. |
| `over-refund` | 422 | More than the amount left to refund. |
| `insufficient-funds` | 422 | The refund can't be funded from your balance. |
| `refund-failed` | 422 | The refund could not be recorded; nothing was returned to the buyer. |

Source: https://docs.paymee.online/guides/refunds/
