PayZuDocs
Boas práticas

Tratamento de erros

Saiba quando repetir uma chamada e quando não: erro seu não se retenta, falha da instituição financeira ou da PayZu pede nova tentativa com espera crescente, e o timeout merece cuidado porque a operação pode ter valido.

A PayZu retorna os códigos HTTP padrão. Sua estratégia depende da categoria.

A tabela completa de códigos HTTP e o catálogo de errorCode, com o que fazer em cada um, estão em Códigos de erro; esta página cobre a estratégia: quando retentar, como fazer backoff e o que logar.

Helper de retry

Retry em 429, 5xx e 424. Nos demais 4xx, nunca.

ATTEMPTS=4
DELAY=1

for i in $(seq 1 $ATTEMPTS); do
  STATUS=$(curl -s -o /tmp/resp.json -w "%{http_code}" \
    -X POST https://api.payzu.processamento.com/v1/pix \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"amount":99.90,"clientReference":"order-1234"}')

  case $STATUS in
    2*) cat /tmp/resp.json; exit 0 ;;
    424|429|5*) sleep $DELAY; DELAY=$((DELAY*2)) ;;
    *) echo "Erro $STATUS"; cat /tmp/resp.json; exit 1 ;;
  esac
done
echo "Max retries excedido"
exit 1
async function withRetry<T>(
  fn: () => Promise<Response>,
  attempts = 4,
): Promise<T> {
  let lastErr: unknown;
  for (let i = 0; i < attempts; i++) {
    try {
      const res = await fn();
      if (res.ok) return res.json();

      if (res.status >= 400 && res.status < 500 && res.status !== 429 && res.status !== 424) {
        const body = await res.text();
        throw new Error(`Client error ${res.status}: ${body}`);
      }
    } catch (err) {
      lastErr = err;
    }

    const delay = Math.min(8000, 1000 * 2 ** i) + Math.random() * 250;
    await new Promise((r) => setTimeout(r, delay));
  }
  throw lastErr ?? new Error('Max retries exceeded');
}

Timeout: a armadilha do "pode ter dado certo"

Timeout não é equivalente a falha. A PayZu pode ter recebido, processado e gravado a transação, e a resposta apenas não voltou. Sua app não sabe.

Solução: use clientReference único e consulte antes de retentar.

async function createOrRetry(orderId: string, amount: number) {
  const ref = `order-${orderId}`;
  try {
    return await withRetry(() => postPix({ amount, clientReference: ref }));
  } catch (err) {
    // pode ter dado certo apesar do erro/timeout
    const existing = await fetch(
      `https://api.payzu.processamento.com/v1/pix?clientReference=${ref}`,
      { headers },
    ).then((r) => (r.ok ? r.json() : null));
    if (existing) return existing;
    throw err;
  }
}

Observabilidade do erro

Sempre logue, no mínimo:

CampoPor quê
requestIdVem nas respostas de erro PayZu. Suporte rastreia direto.
id localSeu identificador (pedido, saque).
id PayZuSe já houver.
endToEndIdÚtil para rastrear no Bacen em disputa.
clientReferenceA chave de correlação universal.
HTTP status + messageA causa raiz quase sempre está em message.
Tentativa N de MDiferencia primeira tentativa de retry.
log.error('PayZu /pix falhou', {
  requestId: body.requestId,
  status: res.status,
  message: body.message,
  clientReference: ref,
  attempt: i + 1,
  attempts,
});

Mensagens de erro úteis para o usuário final

Não exponha message cru. Traduza para algo acionável:

Erro PayZuMensagem para o usuário
401 Unauthorized"Erro de configuração. Contate o suporte com o código requestId."
400 amount must be >= 1"Valor mínimo da cobrança é R$ 1,00."
400 invalid pixKey"Chave Pix inválida. Confira e tente novamente."
424 (instituição financeira)"Instituição financeira instável no momento. Tente em instantes."
429 Too Many Requests"Estamos com muitas requisições. Tente em instantes."
5xx"Sistema temporariamente indisponível. Já estamos olhando."

Armadilhas comuns

ArmadilhaSintoma
Retentar em 400Spam contra a API, mesmo erro N vezes
Retentar em 401 sem rotacionar tokenToken vaza ainda mais no log
Sem backoff (retry imediato em loop)Vira rate limit, depois fica banido
Sem jitter no backoffN clientes batem ao mesmo tempo, "thundering herd"
Tratar timeout como falha definitivaCliente cobra 2x do usuário
Não logar requestIdSuporte não consegue investigar

Abrir suporte com o requestId

Tem o requestId salvo? Manda direto pro time.

Nesta página