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

# Refunds

> Issue full or partial refunds easily

## Create a Refund

Issue full or partial refunds for card and SPEI payments. Process returns, cancellations, and adjustments with a simple API call.

```http theme={null}
POST /v2/payment-orders/:id/refund
x-api-key: YOUR_API_KEY
x-merchant-id: YOUR_MERCHANT_ID
Content-Type: application/json
```

<Warning>
  **PayCash** and **CIE Cash Net** do not support API refunds. Handle returns at merchant level. Card and SPEI support refunds via the API below.
</Warning>

<Note>
  The `amount` field is **required** on every refund request and is expressed in **minor units (centavos)** — e.g. `5000` refunds \$50.00 MXN. There is no "omit for full refund" shorthand: to refund the full payment, pass the full amount.
</Note>

## Partial Refund

Refund part of the original payment amount:

```json theme={null}
{
  "amount": 5000,
  "reason": "Customer requested partial refund"
}
```

**Response:**

```json theme={null}
{
  "id": "cbea8cd4-79f3-43aa-9375-4eef021dda10",
  "status": "COMPLETED",
  "amount": "10000",
  "refundedAmount": "5000",
  "paymentMethodType": "CARD",
  "createdAt": "2025-10-30T12:00:00.000Z",
  "updatedAt": "2025-10-30T13:00:00.000Z"
}
```

<Note>
  The refund is processed asynchronously. The `status` in the response is the payment order status at the time of the request and may not yet reflect the refund — poll the payment order or subscribe to the `payment.refund.success` webhook to confirm the final `PARTIALLY_REFUNDED` / `REFUNDED` status.
</Note>

## Full Refund

Pass the full remaining amount to refund the payment completely:

```json theme={null}
{
  "amount": 10000,
  "reason": "Order cancelled"
}
```

## Split-Tender Orders

If the payment order was created with `allowPartials: true` (multiple payments on one order), you must also pass the `paymentId` of the specific payment to refund:

```json theme={null}
{
  "amount": 5000,
  "paymentId": "9cc9b665-8230-47aa-b3ed-c419ab20e24a",
  "reason": "Customer requested refund"
}
```

If `paymentId` is omitted on a split-tender order, the API responds with a validation error listing the available payment IDs.

## Refund Timeline

### Card Refunds

Funds typically appear in the customer's account within **5-10 business days**. This is standard bank processing time and outside of Cheqpay's control.

<Steps>
  <Step title="Refund Requested">
    You create a refund via the API.
  </Step>

  <Step title="Instant Processing">
    Cheqpay processes the refund immediately (status: `COMPLETED`).
  </Step>

  <Step title="Bank Processing">
    Card network and issuing bank process the refund (5-10 days).
  </Step>

  <Step title="Funds Available">
    Customer sees the refund in their account.
  </Step>
</Steps>

## Multiple Partial Refunds

Issue multiple partial refunds up to the original payment amount:

```
Original payment:  $100.00 MXN (amount: 10000)
Refund 1:          $25.00 (amount: 2500) → Status: PARTIALLY_REFUNDED
Refund 2:          $25.00 (amount: 2500) → Status: PARTIALLY_REFUNDED
Refund 3:          $50.00 (amount: 5000) → Status: REFUNDED (complete)
```

### Example

```bash theme={null}
# First partial refund
curl -X POST https://api.sandbox.cheqpay.mx/pos/v2/payment-orders/cbea8cd4-79f3-43aa-9375-4eef021dda10/refund \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID" \
  -d '{"amount": 2500, "reason": "Partial return"}'

# Second partial refund
curl -X POST https://api.sandbox.cheqpay.mx/pos/v2/payment-orders/cbea8cd4-79f3-43aa-9375-4eef021dda10/refund \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID" \
  -d '{"amount": 2500, "reason": "Additional return"}'

# Final refund
curl -X POST https://api.sandbox.cheqpay.mx/pos/v2/payment-orders/cbea8cd4-79f3-43aa-9375-4eef021dda10/refund \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID" \
  -d '{"amount": 5000, "reason": "Complete order cancellation"}'
```

## Refund Status

Refund progress is reflected in the payment order status:

| Status               | Description                           |
| -------------------- | ------------------------------------- |
| `REFUND_PROCESSING`  | Refund is being processed             |
| `PARTIALLY_REFUNDED` | Part of the payment has been refunded |
| `REFUNDED`           | The full payment has been refunded    |

<Tip>
  Refunds are processed asynchronously — retrieve the payment order or listen for the `payment.refund.success` webhook to confirm completion. The 5-10 day delay for the customer to see the funds is on the banking side.
</Tip>

## Important Rules

<AccordionGroup>
  <Accordion title="Only Paid Payments" icon="check-circle">
    You can only refund payments that have been authorized or captured (order status `COMPLETED` or `PARTIALLY_REFUNDED` for further partial refunds). Pending or failed payments cannot be refunded.
  </Accordion>

  <Accordion title="Refunds Are Final" icon="ban">
    Once processed, refunds cannot be cancelled or reversed. Make sure to verify the amount before refunding.
  </Accordion>

  <Accordion title="Maximum Refund Amount" icon="calculator">
    Total refunds cannot exceed the original payment amount. Attempting to over-refund will result in an error.
  </Accordion>

  <Accordion title="Processing Fees" icon="dollar-sign">
    Processing fees are typically not refunded. You'll still pay the original transaction fee even after issuing a refund.
  </Accordion>
</AccordionGroup>

## Customer Notifications

Customers are automatically notified when refunds are processed (if notifications are enabled):

```json theme={null}
{
  "notificationOptions": {
    "notifyEmail": true,
    "notifyPhone": false
  }
}
```

The notification includes:

* Refund amount
* Original payment reference
* Estimated timeframe for funds to appear
* Support contact information

<Tip>
  Disable automatic notifications if you prefer to send refund notifications from your own system.
</Tip>

## Refund Reasons

Always include a reason for tracking and reporting:

```json theme={null}
{
  "reason": "Customer returned product"
}
```

**Common Reasons:**

* Customer requested refund
* Order cancelled
* Product returned
* Duplicate payment
* Item out of stock
* Service not delivered
* Quality issues

## Error Handling

### Payment Not Refundable

Refunding an order that is not in a refundable state returns a `400` with error code `INVALID_OPERATION`:

```json theme={null}
{
  "error": {
    "code": "INVALID_OPERATION",
    "message": "Payment order is not in a refundable state"
  }
}
```

### Refund Amount Exceeds Available

Attempting to refund more than the remaining refundable amount fails with a message indicating the refund amount exceeds the captured amount. Total successful refunds can never exceed the original captured amount.

### Missing or Invalid Amount

Omitting `amount`, or sending a non-positive value, returns a `400` validation error — `amount` is always required, as an integer in minor units.

## Testing Refunds

### Sandbox Behavior

Refunds in sandbox process instantly (instead of 5-10 days in production):

<Steps>
  <Step title="Create Test Payment">
    Process a payment with a test card.
  </Step>

  <Step title="Issue Refund">
    Call the refund endpoint.
  </Step>

  <Step title="Instant Completion">
    Refund status immediately shows `COMPLETED`.
  </Step>
</Steps>

### Test Scenarios

```bash theme={null}
# Test full refund (pass the full amount)
curl -X POST https://api.sandbox.cheqpay.mx/pos/v2/payment-orders/YOUR_ORDER_ID/refund \
  -H "x-api-key: YOUR_SANDBOX_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID" \
  -d '{"amount": 10000, "reason": "Test full refund"}'

# Test partial refund
curl -X POST https://api.sandbox.cheqpay.mx/pos/v2/payment-orders/YOUR_ORDER_ID/refund \
  -H "x-api-key: YOUR_SANDBOX_KEY" \
  -H "x-merchant-id: YOUR_MERCHANT_ID" \
  -d '{"amount": 5000, "reason": "Test partial refund"}'
```

## Best Practices

<AccordionGroup>
  <Accordion title="Automate Refund Workflows" icon="robot">
    Integrate refunds into your order management system for seamless returns processing.
  </Accordion>

  <Accordion title="Set Refund Policies" icon="file-contract">
    Define clear refund policies and timeframes. Communicate these to customers.
  </Accordion>

  <Accordion title="Track Refund Metrics" icon="chart-line">
    Monitor refund rates to identify issues with products, services, or payment experience.
  </Accordion>

  <Accordion title="Provide Clear Communication" icon="comment">
    Tell customers when to expect refunds in their account (5-10 days for cards).
  </Accordion>

  <Accordion title="Keep Records" icon="folder">
    Store refund reasons and details for accounting and dispute resolution.
  </Accordion>
</AccordionGroup>

## Tracking Refunds

You can track refunds by retrieving the payment order (`GET /v2/payment-orders/:id`) and checking its `status` and `refundedAmount`, or by subscribing to the `payment.refund.pending` and `payment.refund.success` webhook events. Because refunds are processed asynchronously, allow a short delay before the order status reflects the refund.

## Next Steps

<CardGroup cols={2}>
  <Card title="View Payment Orders" icon="file-invoice" href="/features/payment-orders">
    Track payments eligible for refunds
  </Card>

  <Card title="Process Card Payments" icon="credit-card" href="/features/card-payments">
    Learn about payment processing
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/guides/error-handling">
    Handle refund errors gracefully
  </Card>

  <Card title="Best Practices" icon="lightbulb" href="/guides/best-practices">
    Learn refund workflow best practices
  </Card>
</CardGroup>
