# 

## How processor errors reach you

When Paradise Gateway forwards a transaction to a processor, the processor may return its own error or decline code. The API normalizes these into a consistent response, but the raw processor details are available in `processor_response`.

![image](https://files.modern-mermaid.live/images/1776277618995-mermaid-diagram-1776277619066.png)

## Response layers

Every transaction response contains two layers of information:

| Layer | Fields | Purpose |
|  --- | --- | --- |
| **Gateway** | `response_status`, `response_code`, `response_message` | Normalized result you should use for business logic |
| **Processor** | `processor_response` | Raw response from TSYS or Vericheck — useful for debugging |


```json
{
  "id": "TRANSACTION-01KEW32V6YNV11T33XGDR7TGWC",
  "response_status": "declined",
  "response_code": "05",
  "response_message": "Transaction declined by the issuer.",
  "processor_response": {
    "processor_code": "051",
    "processor_message": "INSUFFICIENT FUNDS",
    "auth_response_code": "51"
  },
  "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

## TSYS error codes (card transactions)

Common TSYS auth response codes and what to do:

| Code | Meaning | Action |
|  --- | --- | --- |
| `00` | Approved | Success — fulfill the order |
| `01` | Refer to issuer | Do not retry — the cardholder should call their bank |
| `02` | Refer to issuer (special) | Do not retry — possible fraud alert |
| `05` | Do not honor | Do not retry — ask for a different card |
| `12` | Invalid transaction | Check your request — wrong transaction type? |
| `13` | Invalid amount | Verify the `amount` field |
| `14` | Invalid card number | Card number failed validation at the processor |
| `41` | Lost card — pick up | Do not retry — possible fraud |
| `43` | Stolen card — pick up | Do not retry — possible fraud |
| `51` | Insufficient funds | Ask the customer for a different card |
| `54` | Expired card | Ask for an updated card |
| `61` | Exceeds withdrawal limit | Ask for a different card or smaller amount |
| `65` | Activity limit exceeded | Retry later or ask for a different card |
| `91` | Issuer unavailable | Retry with exponential backoff |


## Vericheck error codes (ACH transactions)

ACH errors are typically ACH return codes from the receiving bank:

| Code | Meaning | Action |
|  --- | --- | --- |
| R01 | Insufficient funds | Wait and retry, or contact customer |
| R02 | Account closed | Request new bank details; delete the token |
| R03 | No account / unable to locate | Request corrected account number |
| R04 | Invalid account number | Validate and re-enter |
| R08 | Payment stopped | Contact customer to resolve |
| R10 | Customer advises not authorized | Do not retry — investigate |
| R29 | Corporate not authorized | Do not retry — contact the business |


See [ACH returns](/docs/tutorials/payment-cookbook/auth-returns) for the full return handling guide.

## Mapping processor errors to actions

Build a mapping function to translate processor codes into your application's actions.

Python
```python
PROCESSOR_ACTION_MAP = {
    "00": "success",
    "01": "refer_to_issuer",
    "05": "decline_no_retry",
    "12": "invalid_request",
    "14": "invalid_card",
    "51": "insufficient_funds",
    "54": "expired_card",
    "91": "retry",
}

NO_RETRY_ACTIONS = {"decline_no_retry", "refer_to_issuer", "invalid_card", "expired_card"}
RETRY_ACTIONS = {"retry"}

def classify_processor_error(processor_response: dict) -> dict:
    code = processor_response.get("auth_response_code", "")
    action = PROCESSOR_ACTION_MAP.get(code, "unknown")

    return {
        "action": action,
        "retryable": action in RETRY_ACTIONS,
        "user_message": get_user_message(action),
        "processor_code": code,
        "processor_message": processor_response.get("processor_message", ""),
    }

def get_user_message(action: str) -> str:
    messages = {
        "success": "Payment successful.",
        "decline_no_retry": "Your card was declined. Please try a different payment method.",
        "refer_to_issuer": "Please contact your bank for assistance.",
        "insufficient_funds": "Insufficient funds. Please try a different card.",
        "expired_card": "Your card has expired. Please use a different card.",
        "invalid_card": "Invalid card number. Please check and re-enter.",
        "invalid_request": "There was an issue with the payment. Please try again.",
        "retry": "Temporary issue. Please try again in a moment.",
    }
    return messages.get(action, "Payment could not be processed. Please try again.")
```

JavaScript
```javascript
const PROCESSOR_ACTION_MAP = {
  "00": "success",
  "01": "refer_to_issuer",
  "05": "decline_no_retry",
  "12": "invalid_request",
  "14": "invalid_card",
  "51": "insufficient_funds",
  "54": "expired_card",
  "91": "retry",
};

const USER_MESSAGES = {
  success: "Payment successful.",
  decline_no_retry: "Your card was declined. Please try a different payment method.",
  refer_to_issuer: "Please contact your bank for assistance.",
  insufficient_funds: "Insufficient funds. Please try a different card.",
  expired_card: "Your card has expired. Please use a different card.",
  invalid_card: "Invalid card number. Please check and re-enter.",
  invalid_request: "There was an issue with the payment. Please try again.",
  retry: "Temporary issue. Please try again in a moment.",
};

function classifyProcessorError(processorResponse) {
  const code = processorResponse?.auth_response_code || "";
  const action = PROCESSOR_ACTION_MAP[code] || "unknown";
  return {
    action,
    retryable: action === "retry",
    userMessage: USER_MESSAGES[action] || "Payment could not be processed.",
    processorCode: code,
    processorMessage: processorResponse?.processor_message || "",
  };
}
```

## Best practices

* **Use `response_status` for flow control** — base your approve/decline/retry logic on the normalized gateway fields, not raw processor codes.
* **Use `processor_response` for diagnostics** — log it for debugging, but don't expose it to customers.
* **Keep your mapping table current** — as you encounter new processor codes in production, add them to your map.
* **Alert on unknown codes** — if `classify_processor_error` returns `unknown`, log an alert so you can investigate.


## Next steps

* [Log and monitor API errors](/docs/tutorials/error-handling/log-monitor-api-errors)
* [Retry with exponential backoff](/docs/tutorials/error-handling/retry-with-exponential-backoff)
* [Handle declined transactions](/docs/tutorials/payment-cookbook/handle-declined-transactions)