# Refunds

```ts
await cash.payments.refund(payment.id);                  // everything not yet refunded
await cash.payments.refund(payment.id, { amount: 50 });  // part of it, in rand
```

Only a **paid** payment can be refunded. callCash reserves the amount on the payment before it asks the gateway, so two refund requests at once can never give back more than was paid. Asking for too much throws `refund_invalid` and says how much is left.

## Yoco

- The refund is sent to Yoco with an idempotency key, so a retry cannot refund twice.
- It comes back `pending`. It becomes `succeeded` when Yoco's `refund.succeeded` event arrives at your webhook route, and your handler gets `refund.succeeded`.
- Only payments made with **live** keys can be refunded. With test keys, `refund()` throws `refund_unsupported`.
- If Yoco refuses, you get `gateway_rejected` and the reserved amount is released, so you can try again.

## Netcash

Netcash Pay Now has no refund call that callCash has verified, so `refund()` throws `refund_unsupported`.

1. Refund the payment in the Netcash portal.
2. Record it, so your totals stay right:

```ts
await cash.payments.recordRefund(payment.id, { amount: 50 }); // leave out amount for the full remainder
```

`recordRefund` marks the refund `succeeded` straight away and your handler gets `refund.succeeded` the next time events are delivered (for example with `cash.events.deliver(onCash)`).

## Refunds made in a gateway's portal

If a refund event arrives for a refund callCash did not start, callCash records it against the payment so `refunded` stays true.

## Test gateway

Refunds on test gateway payments succeed immediately.
