Search by

caiquebispo / focus-nfe

caiquebispo

PHP SDK para a API Focus NFe com suporte multi-CNPJ - Emissão de NFe, NFCe, NFSe, CTe, MDFe e mais

Package info

github.com/caiquebispo/focus-nfe

pkg:composer/caiquebispo/focus-nfe

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-10-06 18:43 UTC

This package is auto-updated.

Last update: 2026-10-06 21:03:52 UTC


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.

Latest Stable Version Total Downloads Latest Unstable Version License PHP Version Require

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:

  1. Registrar o webhook na Focus NFe (dizer para onde enviar)
  2. 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 $except do VerifyCsrfToken middleware:

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