Pular para conteúdo

Problemas Comuns

Soluções rápidas para os problemas mais frequentes.

Webhook não recebe eventos

Sintomas

  • Nayax envia webhook mas não chega no sistema
  • Logs não mostram requisições

Diagnóstico

# Verificar se o serviço está rodando
curl http://localhost:3000/health

# Testar webhook manualmente
curl -X POST http://localhost:3000/webhooks/nayax \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"transactionKey":"test-123","storeCode":"LOJA001",...}'

Soluções

  1. Firewall bloqueando

    # Verificar regras de firewall
    sudo ufw status
    
    # Permitir porta 3000
    sudo ufw allow 3000
    

  2. URL incorreta no Nayax

  3. Verificar se a URL está correta: https://seu-dominio.com/webhooks/nayax
  4. Verificar se tem /webhooks/nayax no final

  5. Token incorreto

    -- Verificar token configurado
    SELECT * FROM "Setting" WHERE key = 'NAYAX_BEARER_TOKEN';
    


Jobs ficam em PENDING

Sintomas

  • Jobs criados mas nunca processados
  • Status permanece PENDING

Diagnóstico

-- Ver jobs pendentes
SELECT id, "eventKey", status, "nextRunAt", "createdAt"
FROM "OutboxJob"
WHERE status = 'PENDING'
ORDER BY "createdAt" DESC
LIMIT 10;

Soluções

  1. JobsProcessor não está rodando

    # Verificar logs
    tail -f logs/app.log | grep "JobsProcessor"
    
    # Deve aparecer:
    # [JobsProcessor] tick: start
    # [JobsProcessor] tick: fetched X job(s)
    

  2. nextRunAt no futuro

    -- Resetar nextRunAt
    UPDATE "OutboxJob"
    SET "nextRunAt" = NOW()
    WHERE status = 'PENDING'
      AND "nextRunAt" > NOW();
    

  3. Credenciais Saipos não configuradas

    -- Verificar configuração do restaurante
    SELECT id, name, "codeStoreSaipos", "saiposIdPartner", "saiposSecret"
    FROM "Restaurant"
    WHERE id = 'restaurant_id';
    
    -- Se null, configurar:
    UPDATE "Restaurant"
    SET "saiposIdPartner" = 'partner_123',
        "saiposSecret" = 'secret_xyz'
    WHERE id = 'restaurant_id';
    


Jobs ficam em PROCESSING

Sintomas

  • Jobs marcados como PROCESSING há muito tempo
  • processingAt com timestamp antigo

Diagnóstico

-- Encontrar jobs travados
SELECT id, "eventKey", "processingAt", "lockedBy"
FROM "OutboxJob"
WHERE status = 'PROCESSING'
  AND "processingAt" < NOW() - INTERVAL '5 minutes'
ORDER BY "processingAt";

Soluções

  1. Aguardar o Reaper (automático)
  2. O reaper roda a cada 20 segundos
  3. Reseta jobs PROCESSING > 2 minutos automaticamente

  4. Reset manual

    -- Resetar jobs travados
    UPDATE "OutboxJob"
    SET status = 'FAILED',
        "nextRunAt" = NOW(),
        "processingAt" = NULL,
        "lockedBy" = NULL,
        "lastError" = 'Manual reset'
    WHERE status = 'PROCESSING'
      AND "processingAt" < NOW() - INTERVAL '5 minutes';
    

  5. Verificar se processo morreu

    # Verificar processos Node rodando
    ps aux | grep node
    
    # Se não houver, reiniciar
    npm run start:prod
    


Erro 400: order_id já existe

Sintomas

Job job_123 DLQ: Request failed with status code 400
Response: {"errorCode":400,"message":"order_id já existe"}

Causa

Em ambiente de teste, o mesmo transactionKey está sendo reutilizado.

Soluções

  1. Usar transações de teste
    {
      "transactionKey": "test-<timestamp>",
      "isTestTransaction": true,  // ← importante!
      ...
    }
    

O sistema adiciona timestamp automaticamente quando isTestTransaction: true

  1. Gerar transactionKey único

    # Usar UUID + timestamp
    curl -X POST http://localhost:3000/webhooks/nayax \
      -d "{\"transactionKey\":\"test-$(date +%s)-$(uuidgen)\",...}"
    

  2. Deletar pedido no Saipos (não recomendado)

    # Último recurso - deletar pedido existente no Saipos
    # Consultar documentação Saipos para endpoint de deleção
    


Erro 401/901/902: Autenticação Saipos

Sintomas

Job failed: Unauthorized
errorCode: 901 ou 902

Diagnóstico

-- Verificar credenciais
SELECT "saiposIdPartner", "saiposSecret"
FROM "Restaurant"
WHERE id = 'restaurant_id';

Soluções

  1. Credenciais expiradas/incorretas

    -- Atualizar credenciais
    UPDATE "Restaurant"
    SET "saiposIdPartner" = 'novo_id',
        "saiposSecret" = 'novo_secret'
    WHERE id = 'restaurant_id';
    

  2. Cache de token inválido

    -- Limpar cache (feito automaticamente pelo sistema)
    DELETE FROM "TokenCache" WHERE provider = 'SAIPOS';
    

  3. Ambiente errado

    # Verificar URL base
    echo $SAIPOS_BASE
    
    # Produção
    export SAIPOS_BASE=https://order-api.saipos.com
    
    # Homologação
    export SAIPOS_BASE=https://homolog-order-api.saipos.com
    


Soma dos pagamentos não fecha

Sintomas

[MAPPER] total_amount: 50.00
[MAPPER] sum of payments: 48.50
[MAPPER] difference: 1.50

Causa

Diferenças de arredondamento ou pagamentos com valores zero.

Solução

O mapper ajusta automaticamente, mas se o problema persistir:

// Verificar se há pagamentos com amount inválido
const payments = ev.payments.filter(p => {
  const amount = Number(p?.amount ?? 0);
  return isFinite(amount) && amount > 0;
});

Diagnóstico

-- Ver evento completo
SELECT *
FROM "NayaxEvent"
WHERE "transactionKey" = 'chave_problema';

-- Ver pagamentos
SELECT *
FROM "Payment"
WHERE "nayaxEventId" = 'event_id';

Jobs vão direto para DLQ

Sintomas

  • Jobs vão para DLQ na primeira tentativa
  • Sem retry

Causa

Erro HTTP 400 ou 422 (erro permanente)

Diagnóstico

-- Ver erro exato
SELECT "eventKey", "lastError"
FROM "OutboxJob"
WHERE status = 'DLQ'
ORDER BY "createdAt" DESC
LIMIT 5;

Soluções baseadas no erro

1. "Campo obrigatório ausente"

{
  "errorCode": 400,
  "message": "campo X é obrigatório"
}

Solução: Verificar evento Nayax e mapper

-- Ver evento
SELECT *
FROM "NayaxEvent"
WHERE "transactionKey" = 'chave';

2. "Código de produto não existe"

{
  "errorCode": 422,
  "message": "integration_code PROD001 não encontrado"
}

Solução: Cadastrar produto no Saipos ou mapear código diferente

3. "Loja não encontrada"

{
  "errorCode": 400,
  "message": "cod_store inválido"
}

Solução: Verificar mapeamento

SELECT "codeStoreNayax", "codeStoreSaipos"
FROM "Restaurant"
WHERE id = 'restaurant_id';

Lentidão no processamento

Sintomas

  • Jobs demoram muito para processar
  • Queue depth crescendo

Diagnóstico

-- Ver quantidade de jobs pendentes
SELECT status, COUNT(*)
FROM "OutboxJob"
GROUP BY status;

-- Ver jobs mais antigos
SELECT "eventKey", "createdAt", NOW() - "createdAt" as age
FROM "OutboxJob"
WHERE status = 'PENDING'
ORDER BY "createdAt"
LIMIT 10;

Soluções

  1. Aumentar frequência do cron

    // Mudar de 20s para 10s
    @Cron('*/10 * * * * *')
    async tick() { ... }
    

  2. Aumentar lote de processamento

    // Mudar de 20 para 50 jobs por vez
    const jobs = await this.prisma.outboxJob.findMany({
      take: 50, // ← aumentar
      ...
    });
    

  3. Escalar horizontalmente

  4. Subir mais instâncias do serviço
  5. Lock otimista evita duplicação

  6. Otimizar queries

    -- Adicionar índices
    CREATE INDEX IF NOT EXISTS idx_outbox_status_nextrun 
    ON "OutboxJob" (status, "nextRunAt");
    


Reprocessar job manualmente

Quando usar

  • Job em DLQ que precisa ser retentado
  • Job com erro corrigido

Como fazer

-- Resetar job específico
UPDATE "OutboxJob"
SET status = 'PENDING',
    "nextRunAt" = NOW(),
    attempts = 0,
    "lastError" = NULL,
    "processingAt" = NULL,
    "lockedBy" = NULL
WHERE id = 'job_id';

Reprocessar lote

-- Todos os jobs DLQ de uma loja
UPDATE "OutboxJob" o
SET status = 'PENDING',
    "nextRunAt" = NOW(),
    attempts = 0
FROM "NayaxEvent" e
WHERE o."eventKey" = e."transactionKey"
  AND e."storeCode" = 'LOJA001'
  AND o.status = 'DLQ';

Queries Úteis de Debug

Ver últimos eventos recebidos

SELECT "transactionKey", "storeCode", "totalAmount", "transactionDate"
FROM "NayaxEvent"
ORDER BY "transactionDate" DESC
LIMIT 20;

Ver taxa de sucesso

SELECT 
  status,
  COUNT(*) as count,
  ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 2) as percentage
FROM "OutboxJob"
WHERE "createdAt" > NOW() - INTERVAL '24 hours'
GROUP BY status;

Ver jobs falhando repetidamente

SELECT "eventKey", attempts, status, "lastError"
FROM "OutboxJob"
WHERE attempts > 3
ORDER BY attempts DESC
LIMIT 10;

Ver tempo médio de processamento

SELECT 
  AVG(EXTRACT(EPOCH FROM ("updatedAt" - "createdAt"))) as avg_seconds
FROM "OutboxJob"
WHERE status = 'SENT'
  AND "createdAt" > NOW() - INTERVAL '1 day';

Próximos Passos