> ## 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.

# Error Handling

> Handle errors gracefully to provide the best customer experience

## Error Format

Handle errors gracefully to provide the best customer experience. All errors include clear codes and messages.

Errors follow a consistent structure:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request parameters",
    "details": [
      {
        "field": "amount",
        "message": "Amount must be positive"
      }
    ]
  }
}
```

## HTTP Status Codes

| Status Code | Meaning               | Common Causes                          |
| ----------- | --------------------- | -------------------------------------- |
| `400`       | Bad Request           | Invalid parameters, validation errors  |
| `401`       | Unauthorized          | Invalid or missing API key             |
| `404`       | Not Found             | Resource doesn't exist                 |
| `409`       | Conflict              | Duplicate resource, conflicting state  |
| `422`       | Unprocessable Entity  | Payment declined, business logic error |
| `429`       | Too Many Requests     | Rate limit exceeded                    |
| `500`       | Internal Server Error | Server error (rare)                    |
| `503`       | Service Unavailable   | Temporary service disruption           |

## Error Codes

### Client Errors (400-499)

| Code                      | Status | Meaning                    | Action                    |
| ------------------------- | ------ | -------------------------- | ------------------------- |
| `VALIDATION_ERROR`        | 400    | Invalid request parameters | Check and fix parameters  |
| `UNAUTHORIZED`            | 401    | Invalid API key            | Verify credentials        |
| `NOT_FOUND`               | 404    | Resource not found         | Check resource ID         |
| `DUPLICATE`               | 409    | Resource already exists    | Use existing resource     |
| `DECLINED`                | 422    | Payment declined by bank   | Try different card        |
| `INSUFFICIENT_FUNDS`      | 422    | Customer has low balance   | Contact customer          |
| `EXPIRED_CARD`            | 422    | Card has expired           | Request new card details  |
| `INVALID_CARD`            | 422    | Invalid card number        | Re-enter card information |
| `AUTHENTICATION_REQUIRED` | 422    | 3DS authentication needed  | Show 3DS challenge        |
| `AUTHENTICATION_FAILED`   | 422    | 3DS verification failed    | Allow customer to retry   |

### Server Errors (500-599)

| Code                  | Status | Meaning         | Action                         |
| --------------------- | ------ | --------------- | ------------------------------ |
| `INTERNAL_ERROR`      | 500    | System error    | Retry request with backoff     |
| `SERVICE_UNAVAILABLE` | 503    | Temporary issue | Retry with exponential backoff |

## Handle Declined Payments

Show customer-friendly messages when payments are declined:

### Declined Payment Response

```json theme={null}
{
  "error": {
    "code": "DECLINED",
    "message": "Payment declined by issuing bank",
    "declineReason": "insufficient_funds"
  }
}
```

### Decline Reasons

| Decline Reason       | Customer-Friendly Message                                           |
| -------------------- | ------------------------------------------------------------------- |
| `insufficient_funds` | "Payment couldn't be processed. Please try a different card."       |
| `invalid_card`       | "Unable to process this card. Please check your card details."      |
| `expired_card`       | "This card has expired. Please use a different card."               |
| `card_declined`      | "Payment couldn't be completed. Please try another payment method." |
| `processing_error`   | "We're having trouble processing this payment. Please try again."   |

### Best Practices

<AccordionGroup>
  <Accordion title="Use Generic Messages" icon="message">
    Don't expose specific decline reasons to customers. Use friendly, generic messages that don't embarrass them.

    ✅ Good: "Payment couldn't be processed. Please try a different card."
    ❌ Bad: "Insufficient funds in your account."
  </Accordion>

  <Accordion title="Suggest Alternatives" icon="arrows-left-right">
    When a payment is declined, suggest trying:

    * Another payment method
    * A different card
    * SPEI bank transfer (for large amounts)
    * PAYCASH cash payment (for unbanked customers)
    * CIE Cash Net deposit (for BBVA cash or bank payments)
  </Accordion>

  <Accordion title="Log Full Details" icon="file-lines">
    Log the complete error response for your records, but show simplified messages to customers.
  </Accordion>

  <Accordion title="Allow Retries" icon="refresh">
    Let customers retry payments. Some declines are temporary (network issues, temporary holds).
  </Accordion>
</AccordionGroup>

### Example: Handle Declined Payment

```javascript theme={null}
async function processPayment(paymentData) {
  try {
    const response = await cheqpay.payments.create(paymentData);
    return { success: true, payment: response };
  } catch (error) {
    // Log full error for debugging
    logger.error('Payment failed', { error, paymentData });
    
    // Show customer-friendly message
    if (error.code === 'DECLINED') {
      return {
        success: false,
        message: 'Payment couldn\'t be processed. Please try a different card.',
        allowRetry: true
      };
    }
    
    // Handle other errors...
  }
}
```

## Retry Failed Requests

For temporary errors, implement retry logic with exponential backoff:

```javascript theme={null}
async function createPaymentWithRetry(data, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await createPayment(data);
    } catch (error) {
      // Only retry server errors
      const isRetryable = 
        error.status === 500 || 
        error.status === 503 ||
        error.code === 'INTERNAL_ERROR' ||
        error.code === 'SERVICE_UNAVAILABLE';
      
      if (!isRetryable || attempt === maxRetries - 1) {
        throw error;
      }
      
      // Exponential backoff: 1s, 2s, 4s
      const delay = Math.pow(2, attempt) * 1000;
      await sleep(delay);
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
```

### Safe Retries with Idempotency

Thanks to idempotency, you can safely retry requests without creating duplicate charges:

```javascript theme={null}
const payment = await createPaymentWithRetry({
  externalId: 'order-12345', // Same ID = safe retry
  amount: 10000,
  currency: 'MXN',
  // ...
});
```

<Tip>
  Always include an `externalId` to prevent duplicate charges when retrying failed requests.
</Tip>

## Validation Errors

Handle validation errors before submitting:

```json theme={null}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request parameters",
    "details": [
      {
        "field": "amount",
        "message": "Amount must be positive"
      },
      {
        "field": "customer.email",
        "message": "Email is invalid"
      }
    ]
  }
}
```

### Client-Side Validation

Validate data before sending to API:

```javascript theme={null}
function validatePaymentData(data) {
  const errors = [];
  
  // Validate amount
  if (!data.amount || data.amount <= 0) {
    errors.push({ field: 'amount', message: 'Amount must be greater than 0' });
  }
  
  // Validate email
  if (!data.customer?.email || !isValidEmail(data.customer.email)) {
    errors.push({ field: 'email', message: 'Valid email required' });
  }
  
  // Validate card number
  if (data.paymentMethod.type === 'card') {
    const cardNumber = data.paymentMethod.options.card.number;
    if (!isValidCardNumber(cardNumber)) {
      errors.push({ field: 'cardNumber', message: 'Invalid card number' });
    }
  }
  
  return errors;
}
```

## Handle Authentication Errors

### Invalid API Key

```json theme={null}
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key"
  }
}
```

**What to check:**

* API key is correct
* Using the right environment (sandbox vs production)
* The `x-api-key` header is present and correctly formatted

### Example

```javascript theme={null}
async function makeRequest(endpoint, data) {
  try {
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        'x-api-key': process.env.CHEQPAY_API_KEY,
        'x-merchant-id': process.env.CHEQPAY_MERCHANT_ID,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });
    
    if (!response.ok) {
      throw await response.json();
    }
    
    return await response.json();
  } catch (error) {
    if (error.code === 'UNAUTHORIZED') {
      logger.error('Invalid API key - check environment configuration');
      // Alert ops team
      sendAlert('API key issue detected');
    }
    throw error;
  }
}
```

## Handle 3DS Errors

### Authentication Required

```json theme={null}
{
  "paymentOrder": {
    "status": "PAYER_AUTHENTICATION_CHALLENGE_REQUIRED"
  },
  "payerAuthentication": {
    "stepUpUrl": "...",
    "jwt": "..."
  }
}
```

This isn't an error - it means 3DS is required. Display the authentication challenge.

### Authentication Failed

```json theme={null}
{
  "error": {
    "code": "AUTHENTICATION_FAILED",
    "message": "Customer failed to complete authentication"
  }
}
```

**What to do:**

* Show friendly message: "We couldn't verify your identity. Please try again."
* Allow customer to retry
* Offer alternative payment method

<Card title="3D Secure Guide" icon="shield" href="/features/3d-secure">
  Learn how to implement 3DS authentication
</Card>

## Rate Limiting

If you exceed rate limits:

```json theme={null}
{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "retryAfter": 60
  }
}
```

### Handle Rate Limits

```javascript theme={null}
async function makeRequestWithRateLimit(endpoint, data) {
  try {
    return await makeRequest(endpoint, data);
  } catch (error) {
    if (error.code === 'RATE_LIMIT_EXCEEDED') {
      const retryAfter = error.retryAfter || 60;
      logger.warn(`Rate limited. Retrying after ${retryAfter}s`);
      
      await sleep(retryAfter * 1000);
      return await makeRequest(endpoint, data);
    }
    throw error;
  }
}
```

<Note>
  Contact [support@cheqpay.mx](mailto:support@cheqpay.mx) if you consistently hit rate limits. We can increase your limits.
</Note>

## Network Errors

Handle network connectivity issues:

```javascript theme={null}
async function makeRequestWithNetworkRetry(endpoint, data, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await makeRequest(endpoint, data);
    } catch (error) {
      // Check if it's a network error
      if (error.code === 'ECONNREFUSED' || 
          error.code === 'ETIMEDOUT' ||
          error.code === 'ENOTFOUND') {
        
        if (attempt < maxRetries - 1) {
          const delay = Math.pow(2, attempt) * 1000;
          logger.warn(`Network error, retrying in ${delay}ms`);
          await sleep(delay);
          continue;
        }
      }
      throw error;
    }
  }
}
```

## Error Logging

Log errors for debugging and monitoring:

```javascript theme={null}
function logError(error, context) {
  logger.error('Payment error', {
    errorCode: error.code,
    errorMessage: error.message,
    statusCode: error.status,
    timestamp: new Date().toISOString(),
    context: {
      externalId: context.externalId,
      amount: context.amount,
      customerId: context.customerId
    },
    // Don't log sensitive data
    // cardNumber: NEVER LOG THIS
  });
  
  // Send to error tracking service
  if (process.env.NODE_ENV === 'production') {
    Sentry.captureException(error, { extra: context });
  }
}
```

<Warning>
  Never log sensitive data like full card numbers, CVCs, or API keys.
</Warning>

## Error Monitoring

Set up alerts for critical errors:

```javascript theme={null}
const errorThresholds = {
  DECLINED: 0.15,        // Alert if >15% decline rate
  INTERNAL_ERROR: 0.01,  // Alert if >1% server errors
  UNAUTHORIZED: 0.001    // Alert immediately
};

function trackError(error) {
  metrics.incrementError(error.code);
  
  const errorRate = metrics.getErrorRate(error.code);
  const threshold = errorThresholds[error.code] || 0.05;
  
  if (errorRate > threshold) {
    sendAlert({
      title: `High ${error.code} rate detected`,
      message: `${error.code} rate: ${(errorRate * 100).toFixed(2)}%`,
      severity: 'high'
    });
  }
}
```

## Best Practices Summary

<AccordionGroup>
  <Accordion title="Use Customer-Friendly Messages" icon="message">
    Show simple, non-technical error messages to customers. Log detailed errors for debugging.
  </Accordion>

  <Accordion title="Implement Retry Logic" icon="arrows-rotate">
    Retry temporary failures with exponential backoff. Use idempotency to prevent duplicates.
  </Accordion>

  <Accordion title="Validate Client-Side" icon="check">
    Validate data before sending to API to provide instant feedback and reduce errors.
  </Accordion>

  <Accordion title="Log Everything" icon="file-lines">
    Log all errors with context for debugging. Never log sensitive data like card numbers.
  </Accordion>

  <Accordion title="Monitor Error Rates" icon="chart-line">
    Track error rates and set up alerts for unusual patterns or critical errors.
  </Accordion>

  <Accordion title="Provide Fallbacks" icon="life-ring">
    Offer alternative payment methods when primary method fails.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Testing Guide" icon="flask" href="/guides/testing">
    Test error scenarios in sandbox
  </Card>

  <Card title="Best Practices" icon="lightbulb" href="/guides/best-practices">
    Follow recommended patterns
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/errors">
    View complete error reference
  </Card>

  <Card title="Contact Support" icon="headset" href="mailto:support@cheqpay.mx">
    Get help with errors
  </Card>
</CardGroup>
