> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.crisscross.money/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.crisscross.money/_mcp/server.

# Full and Partial Refunds

> Learn how to issue both full and partial refunds using the same payment rails as the original transactions with CrissCross.

### Overview

Refunding transactions is a crucial aspect of managing a successful merchant operation. CrissCross supports both full and partial refunds, allowing merchants to process refunds directly through the same payment rails used for the original transactions.

### How CrissCross Executes Refunds

You always call the same `POST /payment/{transactionId}/refund` endpoint, but under the hood CrissCross executes a refund in one of two ways depending on what the originating payment provider supports:

1. **Provider refund.** When the originating provider exposes a refund API, CrissCross calls it directly. This is the preferred path because it reverses the original movement on the rail it came in on.
2. **Payout refund.** When the originating provider does not support refunds, CrissCross automatically converts the refund into a payout back to the original destination (the payer's mobile wallet, bank account, etc.). This is particularly common for **mobile money**, where many providers expose payouts but not refund APIs, and is also used for **bank payment methods** when we hold the full destination account details.

Three practical consequences:

* **No time limit on payout refunds.** Provider refund windows (typically 90–180 days, sometimes shorter) only apply when CrissCross takes the provider-refund path. Payout refunds can be issued whenever the destination is still valid.
* **Unified accounting.** Both types are tracked as refunds against the original transaction (they appear in `refundTransactionIds` and contribute to `totalRefundedValue` on the session). You cannot over-refund a customer regardless of which mechanism executes, because the same balance is enforced for both.
* **Same webhook events on both paths.** A refund only ever emits `refund.*` events — plus `transaction.refunded` on the original payment once it is refunded in full. The one exception is an automatic late-payment refund: the original signals through `transaction.auto_refunded` instead and never fires `transaction.refunded`. A payout refund never emits `payout.*` events; those are reserved for payouts you create through the Payouts API. See [Webhook Events](/webhook-events) for the event-by-event behaviour.

The mechanism CrissCross chose for any given refund is visible on the returned refund transaction; consumers do not need to differentiate between the two paths to issue or track refunds correctly.

### Refund Requirements

* **Available Balances**: To process refunds, merchants must maintain sufficient balances with their payment processors in the relevant markets. Payout refunds additionally require sufficient payout balance for the destination currency.
* **Transaction Eligibility**: Refunds can only be issued against transactions that have been successfully captured and settled. Provider-refund windows apply only on the provider-refund path; payout refunds are not subject to a CrissCross-imposed time limit.

### Creating a Refund

Refunds are created against the **original payment**, identified by its `transactionId` in the path: `POST /payment/{transactionId}/refund`. The request body takes:

* `refundValue` *(optional)* — an object with `minorAmount` (the refund amount in minor units, e.g. cents) and `currency` (ISO 4217). Omit it entirely for a **full refund**; supply it for a **partial refund**. When provided, the currency must match the original payment. This mirrors the `destinationValue` shape used on payouts.
* `reason` *(optional)* — free-form note recorded for reporting and dispute defense.

A refund is itself a transaction in CrissCross, so the response is a standard transaction record — the same shape returned by the Retrieve Payment Status endpoint. Note in particular:

* `transactionId` — the refund's own transaction identifier. Use this to poll the refund's status or correlate webhook events.
* `status` — the refund starts in a non-terminal (pending) state and transitions asynchronously (see below).

#### Refunds Are Asynchronous

Refund creation returns the refund transaction in a non-terminal (pending) state. The refund is then processed against the underlying rail and transitions to exactly one terminal state — `COMPLETED`, `FAILED`, `ERRORED`, or `CANCELLED` — each mapping one-to-one to a `refund.*` webhook event. There are two ways to observe the transition:

* **Poll** `GET /payment/{transactionId}` using the refund's returned `transactionId` — a refund is a transaction, so it is retrieved through the standard payment-status endpoint.
* **Subscribe** to the `refund.completed`, `refund.failed`, `refund.errored`, or `refund.cancelled` webhook events.

When the original payment becomes fully refunded, a `transaction.refunded` event is also delivered against the *original* payment's `transactionId`.

#### Refund Webhook Payload

Each refund webhook is delivered as the standard CrissCross envelope `{ eventType, timestamp, payload }`, where `payload` is the refund transaction record — the same object you get from `GET /payment/{transactionId}`. For example, a `refund.completed` event:

```json
{
  "eventType": "refund.completed",
  "timestamp": "2026-07-07T08:15:30Z",
  "payload": {
    "status": "COMPLETED",
    "transactionId": "01951c8a-9d5e-7f3b-bc4f-6e7d8c9b0a21",
    "message": "Refund completed.",
    "merchantReference": "order-4021",
    "identifiers": {
      "sessionId": "01951c8a-7c3d-7e1f-9d4a-2b3c4d5e6f70"
    }
  }
}
```

The `payload.status` is `COMPLETED`, `FAILED`, `ERRORED`, or `CANCELLED`, matching the event. See the [Webhooks reference](/api-reference) for the full event schemas.

#### Full Refund

A full refund returns the entire remaining refundable balance. Omit `refundValue` altogether — an empty body (or just a `reason`) is enough:

```json
{
  "reason": "Customer returned the item"
}
```

#### Partial Refund

Partial refunds return only a portion of the payment amount, useful for partial returns or post-purchase discounts. Pass the partial amount in `refundValue`:

```json
{
  "refundValue": {
    "minorAmount": 2500,
    "currency": "NGN"
  },
  "reason": "Partial return — one of two items"
}
```

#### Checking Refundability

Before issuing a refund you can check whether a payment is refundable and for how much with `GET /payment/{transactionId}/refundability`. It returns `refundable`, the remaining `refundableAmount` (in minor units), an optional `refundableUntil` deadline, and a machine-readable `notRefundableReason` when the payment cannot be refunded — ideal for rendering a refund affordance in your UI.

When `refundable` is `false`, `notRefundableReason` is one of: `NOT_REFUNDABLE_REASON_NOT_A_PAYMENT`, `NOT_REFUNDABLE_REASON_NOT_COMPLETED`, `NOT_REFUNDABLE_REASON_WINDOW_EXPIRED`, `NOT_REFUNDABLE_REASON_ALREADY_REFUNDED`, `NOT_REFUNDABLE_REASON_REFUND_IN_FLIGHT`, `NOT_REFUNDABLE_REASON_INSUFFICIENT_BALANCE`, or `NOT_REFUNDABLE_REASON_UNSUPPORTED`.

```json
{
  "refundable": true,
  "refundableUntil": "2026-07-01T00:00:00Z",
  "refundableAmount": {
    "minorAmount": 10000,
    "currency": "NGN"
  }
}
```

### Retrieving a Refund

A refund is a transaction, so retrieve it with `GET /payment/{transactionId}` using the `transactionId` returned from creation. The response is a standard transaction record, with `status` reflecting the latest refund state.

### Where Refunds Surface on Other Objects

Refunds are linked back from the records they affect so you don't have to query refunds separately to understand the state of a session or transaction:

* **On the parent transaction:** `refundTransactionIds` is a list of the `transactionId` of every refund processed against that transaction. Pass any of them directly to `GET /payment/{transactionId}`.
* **On the session:** `totalRefundedValue` is a `{ minorAmount, currency }` object reflecting the cumulative amount refunded across all refunds on the session. Use this to check remaining refundable balance before issuing further partial refunds.

### Refund Notifications

When refunds are processed, CrissCross can trigger notifications for:

* **Successful Refunds**: Confirmations sent to both merchants and customers.
* **Balance Alerts**: Notifications for top-ups when balances with processors are insufficient to cover the refund.

### Best Practices

* **Maintain Adequate Balances**: Regularly monitor and top up your balances with payment processors to ensure smooth refund transactions.
* **Communicate with Customers**: Keep customers informed about the status of their refunds to maintain trust and satisfaction.
* **Monitor Refund Trends**: Analyze refund patterns to identify potential issues with products or services early on.

### Conclusion

Efficiently managing refunds is essential for maintaining customer satisfaction and operational compliance. CrissCross provides robust tools to handle full and partial refunds, ensuring merchants can manage their financial transactions effectively.