vagnerkaefer / nfse-nacional
Biblioteca PHP para integração com o Sistema Nacional de Nota Fiscal de Serviço Eletrônica (NFS-e)
Requires
- php: >=8.2
- ext-curl: *
- ext-dom: *
- ext-json: *
- ext-openssl: *
- tecnickcom/tcpdf: ^6.10
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^10.5
This package is auto-updated.
Last update: 2026-08-14 20:13:57 UTC
README
Biblioteca PHP para integração com o Sistema Nacional de Nota Fiscal de Serviço Eletrônica (NFS-e) do governo brasileiro.
Características
- ✅ Emissão de NFS-e (DPS assinada com XMLDSig)
- ✅ Reforma Tributária do Consumo: grupo IBS/CBS na DPS (leiaute 1.01/RTC)
- ✅ Consulta de NFS-e e de DPS
- ✅ Cancelamento de NFS-e (evento e101101)
- ✅ Download do XML da NFS-e
- ✅ Geração do DANFSe v2.0 conforme a NT 008/2026, com bloco IBS/CBS
- ✅ Listagem de NFS-e por faixa de números de DPS
- ✅ Validação criptográfica de assinaturas XMLDSig
- ✅ Suporte a ambientes de Produção e Homologação
- ✅ Certificado A1 (PFX) com autenticação mTLS
- ✅ Suporte ao Simples Nacional
Requisitos
- PHP >= 8.2
- Extensões PHP: OpenSSL, DOM, cURL, JSON
- Certificado Digital A1 (formato .pfx)
- Certificados antigos cifrados com RC2-40-CBC exigem o binário
opensslcom o legacy provider do OpenSSL 3.x disponível no sistema (a biblioteca faz o fallback automaticamente; certificados modernos são lidos em memória, sem depender do binário)
Instalação
composer require vagnerkaefer/nfse-nacional
Uso Básico
1. Configuração
use NFSe\Config\Config; use NFSe\Services\NFSeClient; use NFSe\Models\DPS; $config = new Config( Config::AMBIENTE_PRODUCAO, // ou Config::AMBIENTE_HOMOLOGACAO '/caminho/para/certificado.pfx', // Caminho do certificado A1 getenv('NFSE_CERT_SENHA'), // Senha do certificado (use env var!) '4216909', // Código IBGE do município emissor 'MeuSistema/1.0.0', // Identificação da aplicação ); $client = new NFSeClient($config);
2. Emitir NFS-e
$dps = (new DPS()) ->setTpAmb(Config::AMBIENTE_PRODUCAO) ->setVerAplic('MeuSistema/1.0.0') ->setSerie('900') ->setNDPS('1') ->setDCompet((new DateTime('-1 day'))->format('Y-m-d')) ->setTpEmit(1) // 1=Prestador ->setCLocEmi('4216909'); $dps->setPrestador([ 'cnpj' => '00.000.000/0001-00', 'xNome' => 'RAZAO SOCIAL DO PRESTADOR', 'regTrib' => [ 'opSimpNac' => 3, // 3=Simples Nacional ME/EPP 'regApTribSN' => 1, 'regEspTrib' => 0, ], ]); $dps->setTomador([ 'cnpj' => '11.111.111/0001-11', 'xNome' => 'NOME DO TOMADOR', 'endereco' => [ 'cMun' => '4204202', 'CEP' => '89802-112', 'xLog' => 'Rua Exemplo', 'nLog' => '123', 'xBairro' => 'Centro', ], ]); $dps->setServico([ 'cTribNac' => '010601', 'xDescServ' => 'Consultoria em TI', 'cLocPrestacao' => '4216909', ]); $dps->setValores([ 'vServ' => 1000.00, 'pTotTribSN' => 6.00, // Percentual de tributos (Simples Nacional) ]); $resultado = $client->emitirNFSe($dps); $chaveAcesso = $resultado['chNFSe'] ?? $resultado['chaveAcesso'];
3. Consultar, baixar e cancelar
// Consulta pela chave de acesso (50 dígitos) $nfse = $client->consultarNFSe($chaveAcesso); // Consulta DPS pelo ID (para descobrir a chave de acesso) $dados = $client->consultarDPS($idDPS); // Download do XML (opcionalmente salvando em arquivo) $xml = $client->baixarXML($chaveAcesso, '/tmp/nfse.xml'); // DANFSe v2.0 gerado localmente a partir do XML da NFS-e. // A API oficial de DANFSe foi sobrestada em 03/08/2026 (NT 008/2026). $pdf = $client->baixarPDF($chaveAcesso, '/tmp/danfse.pdf'); // Cancelamento (código do motivo: 1=Erro na emissão, 2=Serviço não prestado, 9=Outros) $client->cancelarNFSe($chaveAcesso, 'Nota emitida por engano', 1); // Eventos da NFS-e $eventos = $client->consultarEventos($chaveAcesso); // Listagem por faixa de números de DPS (1 requisição HTTP por número!) $lista = $client->listarNFSePorFaixa('4216909', '00000000000100', '900', 1, 50);
4. Reforma Tributária: IBS e CBS
O grupo IBSCBS da DPS é opcional. Ao informá-lo, a DPS passa automaticamente
do leiaute 1.00 para o 1.01 (RTC):
$dps->setIbsCbs([ 'finNFSe' => '0', // 0=NFS-e regular, 1=crédito, 2=débito 'cIndOp' => '110101', // código indicador da operação (Anexo VII) 'indDest' => '0', // 0=destinatário é o próprio tomador 'valores' => [ 'gIBSCBS' => [ 'CST' => '000', // situação tributária do IBS/CBS 'cClassTrib' => '000001', // classificação tributária ], ], ]);
No leiaute RTC o campo cNBS (código da NBS) passa a ser obrigatório em
setServico(). Subgrupos como dest, imovel, gReeRepRes, gTribRegular e
gDif seguem a mesma estrutura de arrays aninhados do Anexo VI.
Os campos criados pela NT 009 (notas de ajuste, estorno de crédito, bens
móveis, pagamentos vinculados, regApIBSCBSSN, cAtvSN) ainda não constam
dos XSDs publicados e só são emitidos com o leiaute estendido ligado:
$dps->setLeiauteEstendido(true); // enviar hoje causa rejeição por schema
Tratamento de erros
Todas as falhas lançam exceções de NFSe\Exception\:
use NFSe\Exception\ApiException; // Erro HTTP da API (tem getStatusCode()/getBody()) use NFSe\Exception\CertificadoException; // Problema com o certificado use NFSe\Exception\AssinaturaException; // Problema na assinatura XML use NFSe\Exception\NFSeException; // Base de todas try { $resultado = $client->emitirNFSe($dps); } catch (ApiException $e) { echo "API retornou HTTP {$e->getStatusCode()}: {$e->getBody()}"; } catch (NFSeException $e) { echo "Erro: " . $e->getMessage(); }
Códigos Importantes
Tipo de Emitente (tpEmit)
1- Prestador2- Tomador3- Intermediário
Regime Tributário (opSimpNac)
1- Não Optante pelo Simples Nacional2- MEI3- ME/EPP (Simples Nacional)
Tributação do ISSQN (tribISSQN)
1- Tributável2- Isento3- Imune4- Exigibilidade Suspensa5- Não Tributável
Retenção do ISSQN (tpRetISSQN)
1- Não retido2- Retido pelo tomador3- Retido pelo intermediário
Estrutura do Projeto
src/NFSe/
├── Certificate/
│ └── Certificado.php # Carga do PFX, PEMs temporários, CNPJ do CN
├── Config/
│ └── Config.php # Ambientes, URLs, User-Agent, opções do DANFSe
├── Exception/
│ ├── NFSeException.php # Base
│ ├── ApiException.php # Erros HTTP da API
│ ├── CertificadoException.php
│ └── AssinaturaException.php
├── Http/
│ ├── HttpClient.php # Transporte cURL + mTLS
│ ├── HttpResponse.php
│ └── ResponseParser.php # JSON/XML → array, mapeamento de erros
├── Models/
│ ├── DPS.php # Fachada: setters e versão do leiaute
│ ├── Dps/
│ │ ├── IbsCbsBuilder.php # Grupo IBSCBS (Reforma Tributária)
│ │ ├── PessoaBuilder.php # Prestador, tomador, destinatário, endereços
│ │ ├── ValoresBuilder.php # Serviço, descontos, deduções e tributos
│ │ └── Xml.php # Helpers de montagem do DOM
│ └── PedidoRegistroEvento.php # XML de eventos (cancelamento)
├── Services/
│ └── NFSeClient.php # Verbos da API
└── Utils/
├── AssinaturaDigital.php # XMLDSig: assinar e validar
├── DANFSeDados.php # Extração XML → dados do DANFSe
├── DANFSeGenerator.php # DANFSe v2.0 (NT 008) via TCPDF
└── Ids.php # IDs de DPS e de eventos
assets/
└── logo-nfse.png # Logomarca oficial usada no DANFSe
Exemplos
Veja a pasta examples/ — as credenciais vêm de variáveis de
ambiente (há um .env.example).
Observações Importantes
- Certificado Digital: certificado A1 válido no formato .pfx. Nunca commite o certificado nem a senha — use variáveis de ambiente.
- Códigos IBGE: use os códigos corretos do município (7 dígitos).
- Série: em produção, utilize a série fornecida pela prefeitura (geralmente "900").
- Data de competência: não pode ser posterior à data de emissão.
- Endereço do prestador: quando o prestador é o emitente (tpEmit=1), não informe.
- DANFSe: a API oficial de geração do PDF foi sobrestada em 03/08/2026
(NT 008/2026) — o documento passa a ser produzido pelo próprio emissor, que
é o que
baixarPDF()faz. Para conferir a numeração e os nomes de municípios impressos, passe o mapamunicipiosemsetDanfseOptions(). - IBS/CBS: informar o grupo é opcional em 2026 (as regras de obrigatoriedade
estão suspensas), mas o conteúdo enviado é validado. Ao usar
setIbsCbs(), a DPS passa automaticamente para o leiaute 1.01.
Desenvolvimento
composer install composer test # PHPUnit (testes de unidade) composer phpstan # Análise estática (nível 6, sem baseline) composer cs-check # Code style (PER-CS 2.0) — cs-fix para aplicar
O CI (GitHub Actions) roda a suíte em PHP 8.2, 8.3 e 8.4. O composer.lock
não é versionado (política comum para bibliotecas): o CI instala dependências
frescas dentro dos ranges do composer.json, exercitando o mesmo cenário dos
consumidores.
Há também um teste de integração real contra a homologação, excluído do CI:
NFSE_CERT_PFX=... NFSE_CERT_SENHA=... NFSE_COD_MUN=... NFSE_CNPJ=... \ vendor/bin/phpunit --group integration
Mudanças na v2.1.1
A v2.1.1 refaz o DANFSe sobre a grade de coordenadas do item 2.4.5 da NT 008/2026, reproduzindo o layout do portal nacional. Mudanças de comportamento:
DANFSeDados::formatarCep()usa a máscara da NT (89.990-000, antes89990-000).- Telefone e e-mail do prestador passam a vir de
DPS/infDPS/prest(caminho da NT), com fallback paraemit. - "Local da Prestação" e "Município de Incidência" incluem a UF
(
Município / UF / País). - "Valor Líquido da NFS-e + IBS/CBS" imprime o
vTotNFdo XML; sem o grupo IBS/CBS sai-(antes era calculado comovLiq + IBS + CBS). - A linha "Totais Aproximados dos Tributos" usa apenas
vTotTrib/pTotTrib(opTotTribSNnão a preenche mais) e sai em linha própria, na chavelinhaTotaisAproximadosdeDANFSeDados::extrair(). - Truncamentos seguem os limites de caracteres da NT (37/77/167/1297/1997, com reticências); linhas opcionais do bloco ISSQN sem dados são suprimidas; o canhoto é fixo no pé do formulário.
Migração v2.0 → v2.1
A v2.1 adequa a biblioteca à Reforma Tributária do Consumo e ao novo DANFSe. Mudanças de compatibilidade:
baixarPDF()gera o DANFSe localmente por padrão. O parâmetro$sleepSecondsdeu lugar a$tentarOficial— quem passavasleepSeconds: 20precisa remover o argumento. A API oficial foi sobrestada em 03/08/2026 pela NT 008/2026.- O DANFSe passou a seguir o layout oficial da NT 008 (v2.0). Quem dependia do visual anterior verá um documento diferente — agora conforme a norma.
- A opção
footerTextdo gerador deixou de existir: o rodapé é definido pela NT. Usemunicipios,logoPatheexibirCanhotoemsetDanfseOptions(). - Descontos agora saem no grupo
vDescCondIncond, como exige o schema (antes eram emitidos dentro devServPrest, o que gerava rejeição). DANFSeDados::extrair()devolve um array bem maior, com os blocosissqn,federal,ibscbsedestinatario; valores monetários já vêm formatados e campos ausentes viram-.- Emitir com o grupo IBS/CBS exige informar
cNBSemsetServico().
Migração v1 → v2
A v2 reorganizou a biblioteca. Principais mudanças de compatibilidade:
- Exceções agora são tipadas (
NFSe\Exception\*) em vez de\Exceptiongenérica. AssinaturaDigitalrecebe umNFSe\Certificate\Certificadono construtor (antes recebia caminho e senha do PFX).NFSeClient::gerarIdDPS()foi removido — useNFSe\Utils\Ids::dps().listarNFSePorFaixa()propaga erros de rede/HTTP (antes eram silenciosamente ignorados); apenas 404 é tratado como "não encontrada".- O ID do evento de cancelamento é determinístico (
PRE+ chave +101101), conforme o XSD oficial (antes usava timestamp, com risco de colisão). - User-Agent e metadados do DANFSe são configuráveis via
Config(setUserAgent(),setDanfseOptions()).
Documentação Oficial
Licença
MIT License - veja o arquivo LICENSE para mais detalhes.
Suporte
Para reportar problemas ou sugerir melhorias, abra uma issue no GitHub.
Contribuindo
Contribuições são bem-vindas! Por favor:
- Fork o projeto
- Crie uma branch para sua feature (
git checkout -b feature/MinhaFeature) - Commit suas mudanças (
git commit -m 'Adiciona MinhaFeature') - Push para a branch (
git push origin feature/MinhaFeature) - Abra um Pull Request
Autor
Vagner Kaefer - vagner@kaefer.eng.br
Agradecimentos
- Governo Federal pela documentação e infraestrutura do Sistema Nacional de NFS-e, por não lançar uma portaria em Dezembro, obrigando o uso do ambiente nacional já em Janeiro, época boa de implantar mudanças, evitando que todas as empresas do país tenham suas integrações quebradas do dia pra noite, e não posso deixar de citar, obrigado por fornecer o serviço com eficiência e sem erros em Janeiro (Contem Ironia (e bastante))