> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waffo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Refund Endpoints

> Request refunds and query refund records via REST + GraphQL

Refund endpoints allow merchants to issue refunds for previously succeeded payments. Refunds are modelled as **refund tickets** (the request) and **refund records** (the executed result, written after PSP confirms).

***

## Business Rules

<Note>
  * Refunds must be requested within **14 days** of the original payment success
  * Refund amount can be **partial** (any value `> 0` and `≤ payment.amount`), but must use the **same currency** as the payment
  * The original payment must already be **fully confirmed by the PSP** — calling refund immediately after a payment webhook arrives may transiently fail; retry after a few seconds
  * A payment that already has a pending or succeeded refund **cannot have another refund ticket created** at the same time
  * When the merchant submits the refund (server-to-server via API Key), the ticket is **auto-approved** and goes straight to processing — no manual review step
</Note>

***

## Refund Ticket Status Values

| Status       | Meaning for merchant                                           |
| ------------ | -------------------------------------------------------------- |
| `pending`    | Awaiting Waffo review (only seen when customer self-initiated) |
| `approved`   | Approved, will be dispatched to PSP shortly                    |
| `rejected`   | Rejected — see `rejectReason` in GraphQL                       |
| `processing` | Dispatched to PSP, awaiting upstream completion                |
| `succeeded`  | Funds returned to the customer                                 |
| `failed`     | PSP returned failure — can be reviewed and resubmitted         |

<Tip>
  Merchant-initiated refunds skip `pending` and `approved` entirely — they appear as `processing` immediately after creation.
</Tip>

***

## Business identifiers — link your records to Waffo

You can attach an optional business-side identifier (max **128 characters**) at two creation points; on the wire each one carries its own flat key so a single payload can hold both without ambiguity:

| Created at                    | Request body field               | Stored as                                                                       |
| ----------------------------- | -------------------------------- | ------------------------------------------------------------------------------- |
| `checkout/create-session`     | `orderMerchantExternalId`        | `orders.merchant_external_id` (inherited by first and renewal payments)         |
| `refund-ticket/create-ticket` | `refundTicketMerchantExternalId` | `refund_tickets.merchant_external_id` (inherited by the executed refund record) |

End-to-end propagation (the same flat dual-key names are used everywhere — request bodies, webhook payloads, and GraphQL types):

```
checkout/create-session         (request:  orderMerchantExternalId)
                                    │
                                    ├──► OnetimeOrder / SubscriptionOrder . orderMerchantExternalId
                                    ├──► Payment                          . orderMerchantExternalId   (first + subscription renewals)
                                    │
refund-ticket/create-ticket     (request:  refundTicketMerchantExternalId)
                                    │
                                    ├──► RefundTicket                     . refundTicketMerchantExternalId
                                    │
                                    └──► Refund carries BOTH:
                                         • orderMerchantExternalId           (from the originating order)
                                         • refundTicketMerchantExternalId    (from the originating refund ticket)
```

These identifiers are **not** idempotency keys — Waffo does not enforce uniqueness; the same reference can be attached to multiple records and you are responsible for any uniqueness guarantees on your side.

The same field names appear in three places — request body, webhook payload, GraphQL — so the value you write at checkout/refund-ticket creation can be read back without renaming.

Use these identifiers to **query** Payments, Refunds, and Refund Tickets via GraphQL — see the [GraphQL orders & payments](/api-reference/endpoints/graphql/orders-and-payments) guide.

***

## Endpoints

<CardGroup cols={2}>
  <Card title="Create Refund Ticket" icon="ticket" href="/api-reference/endpoints/refunds/create-refund-ticket">
    Request a refund for a succeeded payment. Supports full and partial refunds.
  </Card>

  <Card title="Resubmit Refund Ticket" icon="rotate" href="/api-reference/endpoints/refunds/resubmit-refund-ticket">
    Revise and resubmit a rejected or failed refund ticket.
  </Card>
</CardGroup>

<Tip>
  Use [GraphQL](/api-reference/endpoints/graphql/orders-and-payments) to query refund tickets and the executed refund records. On `Refund`, the two business numbers are exposed as flat fields `refund.orderMerchantExternalId` and `refund.refundTicketMerchantExternalId` — same names as the webhook payload. Filter executed refunds by the refund ticket reference using `refunds(filter: { refundTicketMerchantExternalId: ... })`.
</Tip>
