# Errors

Every failure is a `CallCashError`:

```ts
import { isCallCashError } from "callcash";

try {
  await cash.payments.create({ amount, reference });
} catch (e) {
  if (isCallCashError(e)) {
    e.code;       // "amount_invalid"
    e.status;     // 422
    e.message;    // says what went wrong and what to do
    e.retryable;  // true when nothing was charged and trying again is safe
  }
  throw e;
}
```

Retry only when `retryable` is `true`. Messages are written for developers; show your customers your own words.

## Codes

| Code | Status | Retry | When | What to do |
|---|---|---|---|---|
| `amount_invalid` | 422 | No | `amount` is not rand above 0 with at most two decimals, or is over R9,999,999.99 | Send rand, like `250` or `99.50`. If you store cents, divide by 100. |
| `reference_invalid` | 422 | No | `reference` is empty or longer than 100 characters | Use your own order number. |
| `reference_used` | 409 | No | The reference was already paid, or is open for a different amount | Use a new reference, or read the existing payment. |
| `gateway_down` | 503 | Yes | A gateway did not answer: network error, a 15 second timeout, a 5xx or 429, or Netcash's status check failed | Retry. Routing already tried your backup gateway. In a webhook, the 503 makes the gateway retry. |
| `gateway_rejected` | 502 | No | Yoco refused to open a checkout or a refund | Read the message, which includes Yoco's answer. The payment is left failed. |
| `signature_invalid` | 400 | No | A Yoco event's signature did not match, it was over three minutes old, or it was not JSON | Check `webhookSecret` is the secret for this webhook. Check your server clock. |
| `not_found` | 404 | No | There is no payment with that id | Check the id. |
| `refund_invalid` | 400 | No | The payment is not paid, is fully refunded, or you asked for more than is left | Refund only what is left. |
| `refund_unsupported` | 400 | No | Netcash payments, or Yoco payments made with test keys | Netcash: refund in the portal, then `recordRefund()`. |
| `config_invalid` | 500 | No | The setup is wrong: see below | Fix the config. |
| `api_error` | varies | When 5xx | The hosted API (planned) answered without a code; also the body of a webhook response when the request could not be read or the store failed | Retry when the status is 5xx. |
| `gateway_limit` | 402 | No | Declared for a gateway's transaction limit. **Not thrown by version 0.1.0.** | Netcash's R100 limit shows as a missing card option, not an error. See [Netcash](https://callcash.riverrr.co.za/docs/netcash#the-r100-limit). |

## config_invalid, in detail

- Real gateways without a `store`.
- Both `key` and `gateways` given.
- A gateway named in `routes` or `gateway` that is not in `gateways`.
- An empty Yoco `secretKey` or Netcash `serviceKey`.
- A Yoco payment with no `returnUrl` or `return_url`.
- A Yoco event arriving with no `webhookSecret` configured.
- `cash.test.pay()` on a payment that is not on the test gateway.
- A `cc_` key that does not start with `cc_test_` or `cc_live_`.
- Calling a hosted-only method (`webhooks.delete`) without a key, or a direct-only method (`webhooks.handle`, `recordRefund`, `events.deliver`, `test.*`) with one.

## handler_failed

Not a thrown error. It is the code in the 500 response `webhooks.handle` gives the gateway when your handler throws. The event stays owed and is delivered again.
