Tratamento de Erros

Aprenda a lidar com erros de forma robusta e implementar retries adequados.

Formato de erro

Todas as APIs Axon retornam erros em um formato consistente:

error-response.jsonjson
{
  "error": {
    "code": "INVALID_REQUEST",
    "message": "The card number is invalid",
    "details": {
      "field": "card.number",
      "reason": "failed_luhn_check"
    },
    "request_id": "req_abc123def456",
    "doc_url": "https://docs.axon.com/errors/INVALID_REQUEST"
  }
}

Codigos HTTP

CodigoDescricaoAcao
200SucessoProcessar resposta
400Request invalidoCorrigir parametros
401Nao autenticadoVerificar API key
403Sem permissaoVerificar permissoes
404Nao encontradoVerificar ID/path
409ConflitoVerificar estado
429Rate limitRetry com backoff
500Erro internoRetry com backoff
503Servico indisponivelRetry com backoff

Implementando retries

retry-handler.jsjavascript
class AxonClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.axon.com';
    this.maxRetries = 3;
  }

  async request(method, path, data, options = {}) {
    let lastError;

    for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
      try {
        const response = await fetch(`${this.baseUrl}${path}`, {
          method,
          headers: {
            'Authorization': `Bearer ${this.apiKey}`,
            'Content-Type': 'application/json',
            ...(options.idempotencyKey && {
              'Idempotency-Key': options.idempotencyKey,
            }),
          },
          body: data ? JSON.stringify(data) : undefined,
        });

        // Sucesso - retorna resposta
        if (response.ok) {
          return response.json();
        }

        const error = await response.json();

        // Erros que nao devem ser retentados
        if ([400, 401, 403, 404, 409].includes(response.status)) {
          throw new AxonError(error.error, response.status);
        }

        // Rate limit - usar Retry-After
        if (response.status === 429) {
          const retryAfter = parseInt(response.headers.get('Retry-After') || '1');
          await this.sleep(retryAfter * 1000);
          continue;
        }

        // Erros 5xx - retry com backoff
        lastError = new AxonError(error.error, response.status);

      } catch (error) {
        if (error instanceof AxonError) throw error;
        lastError = error;
      }

      // Calcular delay com exponential backoff + jitter
      if (attempt < this.maxRetries) {
        const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
        const jitter = Math.random() * 1000;
        await this.sleep(delay + jitter);
      }
    }

    throw lastError;
  }

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

class AxonError extends Error {
  constructor(error, status) {
    super(error.message);
    this.code = error.code;
    this.status = status;
    this.details = error.details;
    this.requestId = error.request_id;
  }
}

Quando fazer retry

Faca retry apenas para erros 429, 500, 502, 503 e 504. Erros 4xx (exceto 429) indicam problemas com o request e nao devem ser retentados.

Tratamento por tipo de erro

error-handling.jsjavascript
async function processPayment(tokenId, amount) {
  try {
    const payment = await axon.request('POST', '/v1/payments', {
      token_id: tokenId,
      amount,
    }, {
      idempotencyKey: generateIdempotencyKey(),
    });

    return { success: true, payment };

  } catch (error) {
    if (error instanceof AxonError) {
      switch (error.code) {
        case 'INVALID_TOKEN':
          // Token invalido ou expirado
          return { success: false, action: 'request_new_card' };

        case 'INSUFFICIENT_FUNDS':
          // Saldo insuficiente
          return { success: false, action: 'notify_user' };

        case 'CARD_DECLINED':
          // Cartao recusado pelo banco
          return { success: false, action: 'try_another_card' };

        case 'RATE_LIMIT_EXCEEDED':
          // Rate limit - ja foi tratado com retry
          return { success: false, action: 'try_later' };

        case 'DUPLICATE_REQUEST':
          // Request duplicado (idempotency)
          // Buscar resultado anterior
          return await getExistingPayment(error.details.existing_id);

        default:
          // Erro desconhecido - logar e notificar
          console.error('Unexpected error:', error);
          return { success: false, action: 'contact_support' };
      }
    }

    // Erro de rede ou outro
    console.error('Network error:', error);
    return { success: false, action: 'retry_later' };
  }
}

Logging e monitoramento

logging.jsjavascript
function logApiError(error, context) {
  const logEntry = {
    timestamp: new Date().toISOString(),
    level: 'error',
    service: 'axon-integration',
    error: {
      code: error.code,
      message: error.message,
      status: error.status,
      requestId: error.requestId,
    },
    context: {
      endpoint: context.endpoint,
      method: context.method,
      customerId: context.customerId,
    },
  };

  // Enviar para sistema de logs
  console.error(JSON.stringify(logEntry));

  // Alertar para erros criticos
  if (error.status >= 500) {
    alertOncall(logEntry);
  }
}

// Metricas para monitoramento
function recordMetric(endpoint, status, duration) {
  metrics.histogram('axon_api_request_duration', duration, {
    endpoint,
    status: String(status),
  });

  if (status >= 400) {
    metrics.increment('axon_api_errors', {
      endpoint,
      status: String(status),
    });
  }
}