caiquebispo / focus-nfe
PHP SDK para a API Focus NFe com suporte multi-CNPJ - Emissão de NFe, NFCe, NFSe, CTe, MDFe e mais
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
- illuminate/support: Para integração automática com Laravel (ServiceProvider + Facade)
Provides
None
Conflicts
None
Replaces
None
README
SDK PHP puro para a API Focus NFe com suporte nativo a múltiplos CNPJs no mesmo sistema. Funciona em qualquer projeto PHP — com integração opcional para Laravel.
Requisitos
- PHP 8.2+
- Guzzle 7+
Instalação
composer require caiquebispo/focus-nfe
Como pacote local (monorepo)
No composer.json do seu projeto, adicione o repositório:
{
"repositories": [
{
"type": "path",
"url": "../focus-nfe"
}
]
}
Depois composer require caiquebispo/focus-nfe.
Configuração (PHP puro)
Crie a instância passando um array de configuração:
use CaiqueBispo\FocusNfe\FocusNfe; $focus = FocusNfe::make([ 'environment' => 'homologacao', // ou 'producao' 'default' => 'matriz', 'empresas' => [ 'matriz' => [ 'cnpj' => '12345678000123', 'tokens' => [ 'homologacao' => 'seu_token_homologacao_matriz', 'producao' => 'seu_token_producao_matriz', ], ], 'filial_sp' => [ 'cnpj' => '12345678000204', 'tokens' => [ 'homologacao' => 'seu_token_homologacao_filial', 'producao' => 'seu_token_producao_filial', ], ], 'filial_rj' => [ 'cnpj' => '12345678000385', 'tokens' => [ 'homologacao' => 'seu_token_homologacao_rj', 'producao' => 'seu_token_producao_rj', ], ], ], 'timeout' => 30, 'retry' => ['times' => 3, 'sleep' => 500], ]);
Integração com Laravel (opcional)
Se estiver usando Laravel, o ServiceProvider é registrado automaticamente via package discovery.
Publicar configuração
php artisan vendor:publish --tag=focus-nfe-config
.env
FOCUS_NFE_ENVIRONMENT=homologacao FOCUS_NFE_DEFAULT_EMPRESA=matriz FOCUS_NFE_MATRIZ_CNPJ=12345678000123 FOCUS_NFE_MATRIZ_TOKEN_HOMOLOGACAO=seu_token_homologacao_matriz FOCUS_NFE_MATRIZ_TOKEN_PRODUCAO=seu_token_producao_matriz FOCUS_NFE_FILIAL_SP_CNPJ=12345678000204 FOCUS_NFE_FILIAL_SP_TOKEN_HOMOLOGACAO=seu_token_homologacao_filial FOCUS_NFE_FILIAL_SP_TOKEN_PRODUCAO=seu_token_producao_filial # Webhook (token para validar callbacks da Focus NFe) FOCUS_NFE_WEBHOOK_SECRET="Bearer meu-token-secreto" FOCUS_NFE_WEBHOOK_PATH=webhooks/focusnfe
Uso via Facade
use CaiqueBispo\FocusNfe\Laravel\Facades\FocusNfe; FocusNfe::empresa('filial_sp')->nfe()->emitir('ref-001', $dados);
Uso via injeção de dependência
use CaiqueBispo\FocusNfe\FocusNfe; class NfeController { public function emitir(FocusNfe $focus) { return $focus->empresa('matriz')->nfe()->emitir('ref-001', $dados); } }
Uso
Selecionando a empresa (CNPJ)
O conceito central: cada operação é feita no contexto de uma empresa.
// Usa a empresa padrão (config 'default') $focus->nfe()->emitir('ref-001', $dados); // Seleciona uma empresa específica pelo slug $focus->empresa('filial_sp')->nfe()->emitir('ref-001', $dados); $focus->empresa('filial_rj')->nfe()->emitir('ref-002', $dados); $focus->empresa('matriz')->nfe()->emitir('ref-003', $dados); // Saber qual CNPJ está ativo $focus->empresa('filial_sp')->cnpjAtual(); // "12345678000204" $focus->empresa('filial_sp')->empresaAtual(); // "filial_sp"
Trocando ambiente na hora
// Forçar produção para uma operação específica $focus->empresa('matriz')->producao()->nfe()->emitir('ref-001', $dados); // Forçar homologação para testar $focus->empresa('filial_sp')->homologacao()->nfe()->emitir('ref-test', $dados);
Exemplos por módulo
NFe (Nota Fiscal Eletrônica)
// Emitir $resultado = $focus->empresa('matriz')->nfe()->emitir('ref-001', [ 'natureza_operacao' => 'Venda de mercadoria', 'data_emissao' => '2026-10-06T10:00:00-03:00', 'tipo_documento' => 1, 'finalidade_emissao' => 1, 'cnpj_emitente' => '12345678000123', 'nome_emitente' => 'Minha Empresa LTDA', 'logradouro_emitente' => 'Rua Principal', 'numero_emitente' => '100', 'bairro_emitente' => 'Centro', 'municipio_emitente' => 'Curitiba', 'uf_emitente' => 'PR', 'cep_emitente' => '80010000', 'inscricao_estadual_emitente' => '1234567890', 'regime_tributario_emitente' => 1, 'nome_destinatario' => 'Cliente LTDA', 'cnpj_destinatario' => '98765432000156', 'logradouro_destinatario' => 'Av. Brasil', 'numero_destinatario' => '500', 'bairro_destinatario' => 'Centro', 'municipio_destinatario' => 'São Paulo', 'uf_destinatario' => 'SP', 'cep_destinatario' => '01310100', 'pais_destinatario' => 'Brasil', 'valor_total' => 150.00, 'valor_produtos' => 150.00, 'items' => [ [ 'numero_item' => 1, 'codigo_produto' => 'PROD-001', 'descricao' => 'Produto Exemplo', 'cfop' => '5102', 'quantidade_comercial' => 2, 'valor_unitario_comercial' => 75.00, 'valor_bruto' => 150.00, 'codigo_ncm' => '94036000', 'unidade_comercial' => 'UN', 'inclui_no_total' => 1, ], ], ]); // $resultado['status'] => 'autorizado' ou 'processando_autorizacao' // Consultar $nfe = $focus->empresa('matriz')->nfe()->consultar('ref-001'); // $nfe['status'], $nfe['chave_nfe'], $nfe['caminho_danfe'] // Consultar com dados completos $nfe = $focus->empresa('matriz')->nfe()->consultar('ref-001', completa: true); // Cancelar (mesmo CNPJ que emitiu!) $focus->empresa('matriz')->nfe()->cancelar('ref-001', 'Pedido cancelado pelo cliente'); // Carta de correção $focus->empresa('matriz')->nfe()->cartaCorrecao('ref-001', 'Corrigir endereço do destinatário...'); // Enviar por e-mail $focus->empresa('matriz')->nfe()->enviarEmail('ref-001', [ 'cliente@email.com', 'financeiro@email.com', ]); // Inutilizar numeração $focus->empresa('matriz')->nfe()->inutilizar([ 'cnpj' => '12345678000123', 'serie' => '1', 'numero_inicial' => '100', 'numero_final' => '110', 'justificativa' => 'Numeração perdida por falha no sistema', ]); // Pré-visualizar DANFe (sem valor fiscal) $focus->empresa('matriz')->nfe()->previsualizarDanfe($dados);
NFCe (Nota Fiscal ao Consumidor)
// Emitir (processamento síncrono) $nfce = $focus->empresa('filial_sp')->nfce()->emitir('venda-1234', [ 'natureza_operacao' => 'Venda ao consumidor', 'data_emissao' => '2026-10-06T14:30:00-03:00', 'tipo_documento' => 1, 'presenca_comprador' => 1, 'consumidor_final' => 1, 'items' => [ [ 'numero_item' => 1, 'codigo_produto' => 'SKU-789', 'descricao' => 'Camiseta P', 'cfop' => '5102', 'quantidade_comercial' => 1, 'valor_unitario_comercial' => 89.90, 'valor_bruto' => 89.90, 'codigo_ncm' => '61091000', 'unidade_comercial' => 'UN', ], ], ]); // Cancelar (até 30 minutos) $focus->empresa('filial_sp')->nfce()->cancelar('venda-1234', 'Cliente desistiu da compra');
NFSe (Nota Fiscal de Serviço)
// Emitir $nfse = $focus->empresa('matriz')->nfse()->emitir('serv-001', [ 'data_emissao' => '2026-10-06T10:00:00-03:00', 'natureza_operacao' => '1', 'optante_simples_nacional' => true, 'prestador' => [ 'cnpj' => '12345678000123', 'inscricao_municipal' => '12345', ], 'tomador' => [ 'cnpj' => '98765432000156', 'razao_social' => 'Empresa Cliente LTDA', 'endereco' => [ 'logradouro' => 'Rua das Flores', 'numero' => '200', 'bairro' => 'Centro', 'codigo_municipio' => '4106902', 'uf' => 'PR', 'cep' => '80010000', ], ], 'servico' => [ 'valor_servicos' => 5000.00, 'iss_retido' => false, 'item_lista_servico' => '0107', 'discriminacao' => 'Desenvolvimento de software sob encomenda', 'codigo_municipio' => '4106902', ], ]); // Consultar $nfse = $focus->empresa('matriz')->nfse()->consultar('serv-001'); // Cancelar $focus->empresa('matriz')->nfse()->cancelar('serv-001', 'Serviço não prestado'); // Reenviar e-mail $focus->empresa('matriz')->nfse()->reenviarEmail('serv-001', ['cliente@email.com']);
NFSe Nacional
$focus->empresa('filial_rj')->nfseNacional()->emitir('nfsen-001', $dados); $focus->empresa('filial_rj')->nfseNacional()->consultar('nfsen-001'); $focus->empresa('filial_rj')->nfseNacional()->cancelar('nfsen-001', 'Justificativa...');
CTe (Conhecimento de Transporte)
// CTe normal $focus->empresa('matriz')->cte()->emitir('cte-001', $dados); // CTe OS (Outros Serviços) $focus->empresa('matriz')->cte()->emitirOs('cte-os-001', $dados); // CTe Simplificado $focus->empresa('matriz')->cte()->emitirSimplificado('cte-simp-001', $dados); // Consultar / Cancelar $focus->empresa('matriz')->cte()->consultar('cte-001'); $focus->empresa('matriz')->cte()->cancelar('cte-001', 'Justificativa do cancelamento'); // Carta de correção $focus->empresa('matriz')->cte()->cartaCorrecao('cte-001', [ 'campo' => 'observacao', 'valor' => 'Texto corrigido...', ]);
MDFe (Manifesto de Documentos Fiscais)
$focus->empresa('matriz')->mdfe()->emitir('mdfe-001', $dados); $focus->empresa('matriz')->mdfe()->consultar('mdfe-001'); $focus->empresa('matriz')->mdfe()->cancelar('mdfe-001', 'Viagem cancelada'); // Incluir condutor durante a viagem $focus->empresa('matriz')->mdfe()->incluirCondutor('mdfe-001', [ 'nome' => 'João Silva', 'cpf' => '12345678901', ]); // Incluir documento fiscal $focus->empresa('matriz')->mdfe()->incluirDfe('mdfe-001', [ 'chave' => '41190612345678000123550010000000221923094166', ]); // Encerrar (obrigatório ao fim da operação) $focus->empresa('matriz')->mdfe()->encerrar('mdfe-001');
NFCom / DCe / NFGás
// Todos seguem o mesmo padrão: emitir, consultar, cancelar $focus->empresa('matriz')->nfcom()->emitir('nfcom-001', $dados); $focus->empresa('matriz')->dce()->emitir('dce-001', $dados); $focus->empresa('matriz')->nfgas()->emitir('nfgas-001', $dados);
Documentos recebidos
NFe Recebidas
// Listar notas recebidas $notas = $focus->empresa('matriz')->nfeRecebidas()->listar([ 'cnpj' => '12345678000123', ]); // Consultar por chave $nfe = $focus->empresa('matriz')->nfeRecebidas()->consultar('chave_44_digitos'); // Baixar em diferentes formatos $focus->empresa('matriz')->nfeRecebidas()->consultarJson('chave...'); $focus->empresa('matriz')->nfeRecebidas()->consultarXml('chave...'); $focus->empresa('matriz')->nfeRecebidas()->consultarPdf('chave...'); // Manifestar (ciência, confirmação, desconhecimento, não realizada) $focus->empresa('matriz')->nfeRecebidas()->manifestar('chave...', 'ciencia'); $focus->empresa('matriz')->nfeRecebidas()->manifestar('chave...', 'confirmacao'); $focus->empresa('matriz')->nfeRecebidas()->manifestar('chave...', 'desconhecimento'); $focus->empresa('matriz')->nfeRecebidas()->manifestar('chave...', 'nao_realizada');
CTe Recebidas
$focus->empresa('matriz')->cteRecebidas()->listar(); $focus->empresa('matriz')->cteRecebidas()->consultar('chave...'); $focus->empresa('matriz')->cteRecebidas()->informarDesacordo('chave...', [ 'observacao' => 'Carga não corresponde ao manifesto', ]);
NFSe Nacional Recebidas
$focus->empresa('matriz')->nfseNacionalRecebidas()->listar(); $focus->empresa('matriz')->nfseNacionalRecebidas()->consultarJson('chave...'); $focus->empresa('matriz')->nfseNacionalRecebidas()->consultarPdf('chave...');
Gestão
Empresas
// Criar empresa na Focus NFe $empresa = $focus->empresas()->criar([ 'nome' => 'Nova Filial LTDA', 'cnpj' => '11222333000144', 'inscricao_estadual' => '123456', 'regime_tributario' => 1, 'logradouro' => 'Rua Nova', 'numero' => '50', 'bairro' => 'Centro', 'municipio' => 'Curitiba', 'cep' => '80010000', 'uf' => 'PR', 'arquivo_certificado_base64' => base64_encode(file_get_contents('certificado.pfx')), 'senha_certificado' => 'senha123', ]); // $empresa['token_producao'], $empresa['token_homologacao'] // Simular criação (dry run) $focus->empresas()->criar($dados, dryRun: true); // Listar / Consultar / Atualizar / Excluir $focus->empresas()->listar(); $focus->empresas()->consultar($id); $focus->empresas()->atualizar($id, ['nome_fantasia' => 'Novo Nome']); $focus->empresas()->excluir($id); // irreversível!
Webhooks
use CaiqueBispo\FocusNfe\Enums\WebhookEvent; // Criar webhook (usa o CNPJ da empresa selecionada automaticamente) $focus->empresa('matriz')->webhooks()->criar( event: WebhookEvent::Nfe, url: 'https://meuapp.com/webhooks/nfe', ); // Com autorização personalizada $focus->empresa('filial_sp')->webhooks()->criar( event: WebhookEvent::Nfse, url: 'https://meuapp.com/webhooks/nfse', authorization: 'Bearer meu-token-secreto', ); // Listar / Consultar / Excluir $focus->webhooks()->listar(); $focus->webhooks()->consultar('hook_id'); $focus->webhooks()->excluir('hook_id');
Backups
// Usa o CNPJ da empresa selecionada $backups = $focus->empresa('matriz')->backups()->consultarPorCnpj(); // Ou informa outro CNPJ $backups = $focus->backups()->consultarPorCnpj('12345678000123');
Recebendo Webhooks
Quando você emite um documento fiscal, a Focus NFe processa de forma assíncrona e notifica sua aplicação via webhook. O fluxo é:
┌─────────────┐ POST /v2/nfe?ref=pedido-123 ┌──────────────┐
│ Seu sistema │ ──────────────────────────────────► │ Focus NFe │
│ │ { status: "processando..." } │ │
│ │ ◄────────────────────────────────── │ │
│ │ │ Processa │
│ │ │ na SEFAZ │
│ │ POST /webhooks/focusnfe │ │
│ │ ◄────────────────────────────────── │ │
│ onAutorizado│ { ref, status, chave_nfe, ... } │ │
│ atualiza DB │ │ │
└─────────────┘ └──────────────┘
O pacote cuida dos dois lados:
- Registrar o webhook na Focus NFe (dizer para onde enviar)
- Receber e processar o callback quando chegar
Passo 1: Registrar o webhook na Focus NFe
Diga à Focus NFe para onde enviar as notificações:
use CaiqueBispo\FocusNfe\Enums\WebhookEvent; // Registrar webhook com token de segurança $focus->empresa('matriz')->webhooks()->criar( event: WebhookEvent::Nfe, url: 'https://meuapp.com/webhooks/focusnfe', authorization: 'Bearer meu-token-secreto', );
Eventos disponíveis:
| Evento | Descrição |
|---|---|
WebhookEvent::Nfe |
NFe autorizada/rejeitada/cancelada |
WebhookEvent::Nfse |
NFSe |
WebhookEvent::NfseNacional |
NFSe Nacional |
WebhookEvent::Cte |
CTe |
WebhookEvent::Mdfe |
MDFe |
WebhookEvent::Nfcom |
NFCom |
WebhookEvent::Dce |
DCe |
WebhookEvent::NfeRecebida |
NFe recebida de terceiros |
WebhookEvent::CteRecebida |
CTe recebido de terceiros |
WebhookEvent::NfseNacionalRecebida |
NFSe Nacional recebida |
WebhookEvent::Inutilizacao |
Inutilização de numeração |
WebhookEvent::NfceContingencia |
NFCe em contingência |
WebhookEvent::NfceConsultaAutomatica |
NFCe consulta automática |
Passo 2: Processar o callback — WebhookPayload e WebhookHandler
O WebhookPayload parseia os dados que a Focus NFe envia:
use CaiqueBispo\FocusNfe\Webhook\WebhookPayload; $payload->ref; // sua referência (ex: 'pedido-123') $payload->cnpj; // CNPJ do emitente $payload->status; // 'autorizado', 'cancelado', 'erro_autorizacao', etc. $payload->event; // WebhookEvent enum (ou null se desconhecido) // Helpers $payload->isAutorizado(); // status === 'autorizado' $payload->isCancelado(); // status === 'cancelado' $payload->isErro(); // status começa com 'erro' // Dados da nota $payload->chaveAcesso(); // chave de 44 dígitos (NFe, CTe, etc.) $payload->caminhoXml(); // URL do XML na Focus NFe $payload->caminhoDanfe(); // URL do DANFe/PDF $payload->protocoloSefaz(); // protocolo da SEFAZ $payload->mensagemSefaz(); // mensagem da SEFAZ // Dados brutos completos $payload->raw; // array com tudo que a Focus NFe enviou
O WebhookHandler registra listeners por status e valida o token de segurança:
use CaiqueBispo\FocusNfe\Webhook\WebhookHandler; $handler = new WebhookHandler(); $handler->onAutorizado(function (WebhookPayload $payload) { /* ... */ }); $handler->onCancelado(function (WebhookPayload $payload) { /* ... */ }); $handler->onErro(function (WebhookPayload $payload) { /* ... */ }); $handler->onQualquer(function (WebhookPayload $payload) { /* ... */ }); $handler->on('processando_autorizacao', function (WebhookPayload $payload) { /* ... */ });
Configuração do Webhook — PHP puro
1. Configure o secret (token de segurança):
$config = [ 'environment' => 'homologacao', 'default' => 'matriz', 'empresas' => [ 'matriz' => [ 'cnpj' => '12345678000123', 'tokens' => [ 'homologacao' => 'seu_token_homologacao', 'producao' => 'seu_token_producao', ], ], ], 'webhook' => [ 'secret' => 'Bearer meu-token-secreto', ], ]; $focus = FocusNfe::make($config);
2. Registre o webhook na Focus NFe (faça isso uma vez):
// registrar_webhook.php — execute uma vez para cadastrar $focus->empresa('matriz')->webhooks()->criar( event: WebhookEvent::Nfe, url: 'https://meuapp.com/webhooks/focusnfe.php', authorization: 'Bearer meu-token-secreto', );
3. Crie o endpoint que recebe o callback:
// public/webhooks/focusnfe.php require_once __DIR__ . '/../../vendor/autoload.php'; use CaiqueBispo\FocusNfe\Webhook\WebhookHandler; use CaiqueBispo\FocusNfe\Webhook\WebhookPayload; use CaiqueBispo\FocusNfe\Exceptions\WebhookAuthorizationException; $handler = new WebhookHandler(); $handler->onAutorizado(function (WebhookPayload $payload) { // Nota autorizada pela SEFAZ! $pdo = new PDO('mysql:host=localhost;dbname=meuapp', 'user', 'pass'); $stmt = $pdo->prepare('UPDATE notas SET status = ?, chave_acesso = ?, xml_url = ?, danfe_url = ? WHERE ref = ?'); $stmt->execute([ 'autorizada', $payload->chaveAcesso(), $payload->caminhoXml(), $payload->caminhoDanfe(), $payload->ref, ]); }); $handler->onCancelado(function (WebhookPayload $payload) { $pdo = new PDO('mysql:host=localhost;dbname=meuapp', 'user', 'pass'); $stmt = $pdo->prepare('UPDATE notas SET status = ? WHERE ref = ?'); $stmt->execute(['cancelada', $payload->ref]); }); $handler->onErro(function (WebhookPayload $payload) { error_log("ERRO FISCAL [{$payload->ref}]: {$payload->status} - {$payload->mensagemSefaz()}"); }); // Processar try { $payload = $handler->handle( input: file_get_contents('php://input'), authorization: $_SERVER['HTTP_AUTHORIZATION'] ?? null, expectedAuthorization: 'Bearer meu-token-secreto', ); http_response_code(200); echo json_encode(['ok' => true]); } catch (WebhookAuthorizationException $e) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); }
Configuração do Webhook — Laravel
1. Configure o .env:
FOCUS_NFE_WEBHOOK_SECRET="Bearer meu-token-secreto" FOCUS_NFE_WEBHOOK_PATH=webhooks/focusnfe
O arquivo config/focus-nfe.php (publicado via vendor:publish) já lê essas variáveis:
// config/focus-nfe.php (trecho) 'webhook' => [ 'secret' => env('FOCUS_NFE_WEBHOOK_SECRET'), 'path' => env('FOCUS_NFE_WEBHOOK_PATH', 'webhooks/focusnfe'), ],
2. Registre o webhook na Focus NFe (faça isso uma vez, por Artisan ou Tinker):
php artisan tinker
use CaiqueBispo\FocusNfe\Laravel\Facades\FocusNfe; use CaiqueBispo\FocusNfe\Enums\WebhookEvent; // Registrar para NFe FocusNfe::empresa('matriz')->webhooks()->criar( event: WebhookEvent::Nfe, url: config('app.url') . '/' . config('focus-nfe.webhook.path'), authorization: config('focus-nfe.webhook.secret'), ); // Registrar para NFSe, CTe, etc. (repita para cada tipo que precisar) FocusNfe::empresa('matriz')->webhooks()->criar( event: WebhookEvent::Nfse, url: config('app.url') . '/' . config('focus-nfe.webhook.path'), authorization: config('focus-nfe.webhook.secret'), );
3. Crie a rota (excluindo do CSRF):
// routes/api.php use App\Http\Controllers\FocusNfeWebhookController; Route::post('webhooks/focusnfe', [FocusNfeWebhookController::class, 'handle']);
Laravel 11+ — exclua do CSRF no
bootstrap/app.php:->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: [ 'webhooks/*', ]); })Laravel 10 — adicione no
$exceptdoVerifyCsrfTokenmiddleware:protected $except = ['webhooks/*'];
4. Crie o Controller:
// app/Http/Controllers/FocusNfeWebhookController.php <?php namespace App\Http\Controllers; use App\Models\Nota; use CaiqueBispo\FocusNfe\Exceptions\WebhookAuthorizationException; use CaiqueBispo\FocusNfe\Webhook\WebhookHandler; use CaiqueBispo\FocusNfe\Webhook\WebhookPayload; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; class FocusNfeWebhookController extends Controller { public function handle(Request $request, WebhookHandler $handler): JsonResponse { $handler->onAutorizado(function (WebhookPayload $payload) { Nota::where('ref', $payload->ref)->update([ 'status' => 'autorizada', 'chave_acesso' => $payload->chaveAcesso(), 'xml_url' => $payload->caminhoXml(), 'danfe_url' => $payload->caminhoDanfe(), 'protocolo_sefaz' => $payload->protocoloSefaz(), ]); Log::info("NFe autorizada: {$payload->ref} — chave: {$payload->chaveAcesso()}"); }); $handler->onCancelado(function (WebhookPayload $payload) { Nota::where('ref', $payload->ref)->update([ 'status' => 'cancelada', ]); Log::info("NFe cancelada: {$payload->ref}"); }); $handler->onErro(function (WebhookPayload $payload) { Nota::where('ref', $payload->ref)->update([ 'status' => 'erro', 'erro_mensagem' => $payload->mensagemSefaz(), ]); Log::error("Erro fiscal: {$payload->ref} — {$payload->status}: {$payload->mensagemSefaz()}"); }); try { $handler->handle( input: $request->all(), authorization: $request->header('Authorization'), expectedAuthorization: config('focus-nfe.webhook.secret'), ); return response()->json(['ok' => true]); } catch (WebhookAuthorizationException) { Log::warning('Webhook Focus NFe com Authorization inválido', [ 'ip' => $request->ip(), ]); return response()->json(['error' => 'Unauthorized'], 401); } } }
5. (Opcional) Crie um comando Artisan para registrar webhooks:
// app/Console/Commands/FocusNfeRegistrarWebhooks.php <?php namespace App\Console\Commands; use CaiqueBispo\FocusNfe\Enums\WebhookEvent; use CaiqueBispo\FocusNfe\FocusNfe; use Illuminate\Console\Command; class FocusNfeRegistrarWebhooks extends Command { protected $signature = 'focusnfe:webhooks {empresa=matriz : Slug da empresa} {--events=nfe,nfse,cte : Eventos separados por vírgula}'; protected $description = 'Registra webhooks na Focus NFe para a empresa informada'; public function handle(FocusNfe $focus): void { $empresa = $this->argument('empresa'); $events = explode(',', $this->option('events')); $url = config('app.url') . '/' . config('focus-nfe.webhook.path'); $secret = config('focus-nfe.webhook.secret'); foreach ($events as $event) { $webhookEvent = WebhookEvent::from(trim($event)); $focus->empresa($empresa)->webhooks()->criar( event: $webhookEvent, url: $url, authorization: $secret, ); $this->info("Webhook [{$webhookEvent->value}] registrado para [{$empresa}] → {$url}"); } } }
Uso:
# Registrar webhooks de NFe para a matriz php artisan focusnfe:webhooks # Registrar múltiplos eventos para a filial php artisan focusnfe:webhooks filial_sp --events=nfe,nfse,cte,mdfe # Listar webhooks cadastrados (via tinker) php artisan tinker >>> FocusNfe::webhooks()->listar()
APIs Acessórias
// CEP $focus->ceps()->consultarPorCodigo('80010000'); $focus->ceps()->consultar(['uf' => 'PR', 'municipio' => 'Curitiba']); // CFOP $focus->cfops()->consultarPorCodigo('5102'); $focus->cfops()->consultar(['descricao' => 'venda']); // CNAE $focus->cnaes()->listar(); $focus->cnaes()->consultarPorCodigo('6201501'); // CNPJ $focus->cnpjs()->consultar('12345678000123'); // Municípios $focus->municipios()->listar(['uf' => 'PR']); $focus->municipios()->consultarPorCodigo('4106902'); $focus->municipios()->listarServicos('4106902'); $focus->municipios()->consultarServico('4106902', '0107'); $focus->municipios()->listarCodigosTributarios('4106902'); $focus->municipios()->jsonExemplo('4106902'); // NCM $focus->ncms()->consultar(['descricao' => 'camiseta']); $focus->ncms()->consultarPorCodigo('61091000');
Tratamento de erros
use CaiqueBispo\FocusNfe\Exceptions\FocusNfeException; use CaiqueBispo\FocusNfe\Exceptions\FocusNfeValidationException; use CaiqueBispo\FocusNfe\Exceptions\EmpresaNaoConfiguradaException; try { $focus->empresa('filial_sp')->nfe()->emitir('ref-001', $dados); } catch (EmpresaNaoConfiguradaException $e) { // Slug não existe na configuração // "Empresa [filial_sp] não está configurada..." } catch (FocusNfeValidationException $e) { // Erro de validação do schema XML foreach ($e->validationErrors() as $erro) { echo "{$erro['campo']}: {$erro['mensagem']}"; } } catch (FocusNfeException $e) { $e->statusCode; // HTTP status (400, 401, 404, 422) $e->codigo; // 'requisicao_invalida', 'permissao_negada', etc. $e->getMessage(); // Mensagem descritiva $e->responseBody; // Array completo da resposta // Helpers $e->isUnauthorized(); // Token inválido $e->isNotFound(); // Nota não encontrada $e->isAlreadyProcessed(); // Nota já foi autorizada $e->isPending(); // Nota ainda em processamento }
Cenário real: sistema com 4 CNPJs
$focus = FocusNfe::make($config); // A loja do pedido determina qual CNPJ emite $empresa = $pedido->loja->focus_nfe_slug; // ex: 'filial_sp' // Emitir $resultado = $focus->empresa($empresa)->nfe()->emitir( "pedido-{$pedido->id}", $dadosNfe, ); // Cancelar — usa o mesmo slug da empresa que emitiu $focus->empresa($pedido->loja->focus_nfe_slug)->nfe()->cancelar( "pedido-{$pedido->id}", 'Pedido cancelado pelo cliente', ); // Consultar status $status = $focus->empresa($empresa)->nfe()->consultar("pedido-{$pedido->id}");
Ambientes da API
| Ambiente | URL Base |
|---|---|
| Homologação (testes) | https://homologacao.focusnfe.com.br |
| Produção | https://api.focusnfe.com.br |
Todos os endpoints usam o prefixo /v2. A autenticação é HTTP Basic com o token da empresa como usuário e senha vazia.
Módulos disponíveis
| Módulo | Método | Operações |
|---|---|---|
| NFe | nfe() |
emitir, consultar, cancelar, carta de correção, email, inutilizar, importar, DANFe, eventos, ECONF |
| NFCe | nfce() |
emitir, consultar, cancelar, email, inutilizar, ECONF |
| NFSe | nfse() |
emitir, consultar, cancelar, reenviar email |
| NFSe Nacional | nfseNacional() |
emitir, consultar, cancelar, reenviar email |
| CTe | cte() |
emitir, emitir OS, emitir simplificado, consultar, cancelar, carta de correção |
| MDFe | mdfe() |
emitir, consultar, cancelar, incluir condutor, incluir DFe, encerrar |
| NFCom | nfcom() |
emitir, consultar, cancelar |
| DCe | dce() |
emitir, consultar, cancelar |
| NFGás | nfgas() |
emitir, consultar, cancelar |
| Empresas | empresas() |
criar, listar, consultar, atualizar, excluir |
| Webhooks | webhooks() |
criar, listar, consultar, excluir |
| Webhook Handler | WebhookHandler |
receber callbacks, validar authorization, listeners por status |
| Backups | backups() |
consultar por CNPJ |
| NFe Recebidas | nfeRecebidas() |
listar, consultar, JSON/XML/PDF, manifestar, eventos |
| CTe Recebidas | cteRecebidas() |
listar, consultar, JSON/XML/PDF, desacordo |
| NFSe Nac. Recebidas | nfseNacionalRecebidas() |
listar, JSON/XML/PDF/HTML |
| CEPs | ceps() |
consultar, consultar por código |
| CFOPs | cfops() |
consultar, consultar por código |
| CNAEs | cnaes() |
listar, consultar por código |
| CNPJs | cnpjs() |
consultar |
| Municípios | municipios() |
listar, consultar, serviços, códigos tributários, JSON exemplo |
| NCMs | ncms() |
consultar, consultar por código |
Licença
MIT