Retry Policy no SUOT Fiscal SDK para falhas transitórias
Configure a RetryPolicy do SUOT Fiscal SDK para retentativas automáticas em falhas de infraestrutura como timeouts e erros HTTP 5xx do SEFAZ.
A RetryPolicy do SUOT Fiscal SDK foi projetada para lidar com falhas transitórias de infraestrutura — situações em que a comunicação com a SEFAZ não foi concluída por razões técnicas fora do controle da aplicação. Ela não substitui o tratamento de rejeições de negócio: se a SEFAZ devolveu um cStat, o documento foi processado e um retry sem correção dos dados resultará no mesmo código de rejeição.
Use RetryPolicy apenas quando uma InfrastructureException for lançada. Falhas retornadas em Result com isRejected() não devem ser retriadas automaticamente.
2
Verifique se a falha é transitória
Timeouts, erros HTTP 503/502/500 e resets de conexão costumam ser transitórios. Erros de certificado expirado ou credenciais inválidas são permanentes — retry não resolve.
3
Consulte antes de reenviar
Para documentos fiscais, antes de reenviar após uma InfrastructureException, considere consultar a chave do DF-e na SEFAZ. O documento pode ter sido autorizado mesmo sem você receber a resposta.
As situações abaixo não devem ser retriadas automaticamente. Retry sem correção prévia resultará no mesmo erro indefinidamente, gerando consumo desnecessário de recursos e possíveis bloqueios.
Rejeições SEFAZ (cStat 110, 204, 225, 539, etc.) — retornadas em Result, não são exceções
Certificado digital expirado ou inválido — CertificateException
Credenciais de ambiente incorretas — AuthenticationException
XML com schema inválido — o erro será o mesmo na próxima tentativa
CNPJ ou IE inválidos — rejeição de negócio, requer correção dos dados
CT-e e NF-e possuem uma chave de acesso (chave DFe) de 44 dígitos que identifica unicamente o documento. Reenviar o mesmo XML com a mesma chave resultará no código cStat 204 (Duplicidade de NF-e) caso o documento já tenha sido autorizado — o que é um comportamento esperado da SEFAZ, não um erro.O SUOT Fiscal SDK trata isso da seguinte forma:
Antes de reenviar após InfrastructureException: o SDK pode, opcionalmente, executar uma consulta pela chave para verificar se o documento já foi autorizado antes de submeter novamente.
Se cStat 204 for retornado no retry: o SDK interpreta isso como sucesso implícito e tenta recuperar o XML autorizado via consulta, disponibilizando-o no Result.
use SUOT\Fiscal\Retry\RetryPolicy;// Habilite a verificação de idempotência antes do reenvio$policy = RetryPolicy::exponential( maxAttempts: 3, baseDelayMs: 500, checkIdempotencyBeforeRetry: true, // consulta chave antes de reenviar);
A consulta de idempotência consome uma requisição adicional ao webservice da SEFAZ. Em ambientes de alto volume, avalie o impacto no consumo de cota e no tempo de resposta total antes de habilitar essa opção em todos os Handlers.
Em aplicações Laravel, prefira processar autorizações em jobs com dispatch()->onQueue(). Use o sistema de retry nativo do Laravel com backoff exponencial para delegar o controle de tentativas à fila, mantendo a lógica de negócio simples.
// app/Jobs/AutorizarNFeJob.phpnamespace App\Jobs;use Illuminate\Bus\Queueable;use Illuminate\Contracts\Queue\ShouldQueue;use Illuminate\Queue\InteractsWithQueue;use SUOT\Fiscal\Handler\AutorizacaoHandler;use SUOT\Fiscal\Command\AutorizarNFeCommand;use SUOT\Fiscal\Exception\InfrastructureException;class AutorizarNFeJob implements ShouldQueue{ use Queueable, InteractsWithQueue; // Máximo de tentativas pelo sistema de filas do Laravel public int $tries = 5; // Backoff exponencial em segundos: 10s, 20s, 40s, 80s, 160s public function backoff(): array { return [10, 20, 40, 80, 160]; } public function __construct( private readonly AutorizarNFeCommand $command ) {} public function handle(AutorizacaoHandler $handler): void { $result = $handler->handle($this->command); if ($result->isSuccess()) { // Persista e finalize return; } if ($result->isRejected()) { // Rejeição de negócio — não retente via fila, encaminhe para correção $this->fail( new \RuntimeException("Rejeição SEFAZ cStat={$result->status()}: {$result->motivo()}") ); return; } } public function failed(InfrastructureException $exception): void { logger()->critical('NF-e não autorizada após todas as tentativas do job', [ 'error' => $exception->getMessage(), ]); }}