quantumtecnology / nfse-nacional
Pacote para geração de NFSe Nacional usando componentes NFePHP (https://github.com/nfephp-org)
Requires
- php: ^8.1
- ext-curl: *
- ext-dom: *
- ext-mbstring: *
- ext-openssl: *
- ext-zlib: *
- nesbot/carbon: ^3.8 | ^2.0
- nfephp-org/sped-common: ^5.1
- nfephp-org/sped-da: dev-master
- symfony/var-dumper: ^7.1|^6.4
- tecnickcom/tcpdf: ^6.7
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.89
- laravel/pint: ^1.0
- phpunit/phpunit: ^12.0
This package is not auto-updated.
Last update: 2026-07-31 21:44:50 UTC
README
Pacote PHP para emissão, consulta e geração de DANFSe da NFS-e Padrão Nacional (nfse.gov.br), construído sobre os componentes NFePHP.
Este é um fork mantido pela Quantum Tecnology do excelente trabalho da equipe Hadder (ver Créditos). Partindo daquela base, evoluímos o pacote com foco em robustez em produção, suporte a prefeituras com emissor próprio e geração de DANFSe local — recursos detalhados abaixo.
Em uso em produção, porém em evolução contínua. Contribuições são bem-vindas via PR.
✨ Melhorias desta versão (Quantum)
Além de tudo que já existia no pacote original, esta versão adiciona:
🏛️ Destaque de IBS/CBS (Reforma Tributária — EC 132/2023)
O grupo <IBSCBS> do DPS é gerado a partir de $std->infDPS->IBSCBS.
🚨 A partir de 01/08/2026 este grupo é obrigatório — sem ele a NFS-e é rejeitada na recepção. No pacote ele continua sendo emitido apenas quando presente no payload (sem o campo, o XML sai como antes), então a responsabilidade de preenchê-lo é de quem integra. Veja o calendário.
No DPS você apenas declara a situação tributária — CST e cClassTrib. As alíquotas e os valores de IBS/CBS são calculados pelo Ambiente de Dados Nacional e voltam no <infNFSe> da NFS-e autorizada; não envie valores.
$std->infDPS->IBSCBS = (object) [ 'finNFSe' => '0', // 0 = regular (único valor aceito no schema v1.01) 'indFinal' => '0', // 0 = não é consumidor final 'cIndOp' => '030101', // 6 dígitos (tabela oficial) 'indDest' => '0', // 0 = tomador é o destinatário 'valores' => (object) [ 'trib' => (object) [ 'gIBSCBS' => (object) [ 'CST' => '000', // 3 dígitos 'cClassTrib' => '000001', // 6 dígitos ], ], ], ];
Grupos opcionais suportados: tpOper, gRefNFSe (até 99 chaves), tpEnteGov, dest (com endereço nacional ou exterior), imovel (cCIB ou endereço), gReeRepRes (até 1000 documentos), cCredPres, gTribRegular e gDif.
⚠️ Os códigos são strings, nunca inteiros.
CST,cClassTrib,cIndOpecCredPrestêm zero à esquerda significativo — um cast parainttransforma'000001'em1e a nota é rejeitada.
📅 Calendário da Reforma — o que entra e o que sai, e quando
| Data | O que muda | Impacto em quem emite |
|---|---|---|
| 01/08/2026 | O grupo <IBSCBS> passa a ser obrigatório para autorização/recepção da NFS-e |
🚨 Emissão sem o grupo é rejeitada. Quem hoje não preenche $std->infDPS->IBSCBS para de emitir |
| 01/08/2026 | tpRetPisCofins deixa de aceitar os valores 1 e 2 |
Use os códigos novos (3 a 9) ou 0 |
| 01/01/2027 | IBSCBS passa a ser obrigatório também para optantes do Simples Nacional (até lá, dispensados) |
Emissores do Simples precisam preencher o grupo |
| 01/01/2027 | IBS/CBS passam a ser cobrados "por fora": vTotNF = vLiq + vCBS + vIBSTot (em 2026 é só vLiq) |
Muda o valor total da nota — atenção a conciliação e integrações |
| 01/01/2027 | PIS/COFINS saem da base do IBS/CBS: vBC deixa de subtrair vPIS/vCOFINS |
Base de cálculo aumenta |
| até 2032 | Período de transição da EC 132/2023 | Convivência dos dois regimes |
As datas acima constam do Anexo VI da NT SE/CGNFS-e nº 009 (leiaute RTC IBS/CBS v1.04) e da NT SE/CGNFS-e nº 007, de 07/02/2026. Confirme sempre no portal da NFS-e — o cronograma da Reforma vem sendo ajustado.
🔭 Já previsto na NT 009, ainda não implementado
Estes campos existem no leiaute v1.04 mas não nos XSDs v1.01 que o pacote empacota. Serão adicionados quando os schemas v1.04 forem publicados:
| Campo / grupo | Para quê |
|---|---|
indZFMALC |
Zona Franca de Manaus / ALC — alíquota zero de CBS |
indDoacao + gEstornoCred |
operação de doação e estorno dos créditos de entrada |
gIBSCBSAjuste |
notas de ajuste (vIBS, vCBS) |
gPagAntecipado |
referência a NFS-e de pagamento antecipado |
gPgtoVinc |
vinculação com a transação de pagamento (PIX, cartão) |
bensMoveis |
locação de bens móveis (cTribNac 99.04.01) |
Dois pontos mudam estrutura e por isso aguardam os novos schemas: finNFSe migra de IBSCBS/finNFSe para infDPS/finNFSe (passando a aceitar 0 regular, 1 crédito e 2 débito), e o grupo imovel é redesenhado em cMun + gLocacao + gUnidImob.
🧭 Roteamento inteligente por município (emissão × cancelamento × consulta)
Municípios com emissor próprio (ex.: Americana-SP) aceitam o leiaute nacional, mas num endpoint específico da prefeitura — enquanto as consultas continuam no Ambiente Nacional (ADN/SEFIN).
O pacote separa essas responsabilidades automaticamente:
- Emissão / cancelamento → usam a URL configurada em
storage/prefeituras.json(override da prefeitura). - Consultas por chave → usam sempre o Ambiente Nacional central.
Isso elimina os erros 404 que ocorriam quando a consulta era enviada, por engano, ao endpoint de emissão da prefeitura.
⚠️ Emissão e cancelamento costumam ter endpoints DIFERENTES. Em Americana-SP, por exemplo, a emissão vai para
.../api/adn/dps/recepcaoe o cancelamento (evento) para.../api/adn/dps/evento. Por isso ostorage/prefeituras.jsonseparaurls(base) deoperations(caminho por operação): a URL final ébase + "/" + operação. Secancelar_nfseficar vazio, o evento de cancelamento é postado na base de emissão e a prefeitura rejeita com "DPS inválido ou não informado". Veja Configuração da prefeitura.
🧾 DANFSe local (sem depender do ADN)
Geração do PDF da DANFSe diretamente a partir do XML, sem precisar baixar o PDF oficial do Ambiente Nacional:
Danfse— DANFSe completa a partir do XML autorizado.DanfseSimples— renderização tolerante (lê só a estrutura, não exige assinatura), ideal para rascunhos, prévias e notas recebidas via DFe.
Útil quando o ADN está indisponível, para pré-visualização antes da transmissão, ou para notas importadas que não têm PDF oficial salvo.
✅ Validação contra os XSDs oficiais
Os schemas em storage/schemes/ eram arquivos órfãos: o pacote assinava e transmitia XML fora do schema, e o erro só aparecia como rejeição genérica da SEFAZ (L2103). Agora toda transmissão é validada antes de assinar, seguindo o mesmo padrão do sped-nfe.
enviaDps() e cancelaNfse() lançam SchemaValidationException — com o elemento exato apontado — em vez de mandar um documento inválido para a SEFAZ:
use QuantumTecnology\NfseNacional\SchemaValidationException; try { $retorno = $tools->enviaDps($dps->render()); } catch (SchemaValidationException $e) { // ["Element 'cServ': Missing child element(s). Expected is ( cNBS ). (linha 1)"] $erros = $e->getErrors(); }
Para validar por conta própria, antes de transmitir:
$xml = $dps->render(); if ($erros = $dps->validate($xml)) { // [] quando válido // trata sem gastar uma ida à SEFAZ } $dps->getErrors(); // campos obrigatórios que ficaram vazios na montagem
O XSD é escolhido pela versão declarada no próprio XML (<DPS versao="...">), então a convivência de v1.01 e futuras v1.04 funciona sem configuração. Se o schema de uma versão não estiver no pacote, a emissão não é bloqueada — mesma decisão do sped-nfe. Para desligar: $tools->setValidateSchema(false).
🛡️ Validação que falha cedo e com mensagem clara
cTribNacobrigatório (6 dígitos) é validado na montagem do DPS. Antes, um valor ausente gerava uma tag<cTribNac/>vazia e a SEFAZ rejeitava comL2103(XML fora do schema) — erro difícil de diagnosticar. Agora você recebe umaInvalidArgumentExceptionclara, antes de assinar e transmitir.getOperation()e a resolução de URL lançam exceção em chaves/origens desconhecidas, em vez de falhar silenciosamente.
🏷️ Namespace próprio
O namespace passou a ser QuantumTecnology\NfseNacional, alinhado aos demais pacotes da Quantum.
⚠️ Breaking change ao migrar de versões anteriores: troque os
use Hadder\NfseNacional\...poruse QuantumTecnology\NfseNacional\....
🔁 Eventos: cancelamento e cancelamento por substituição
O renderEvento() nunca gerou XML válido em versões anteriores: faltava o elemento obrigatório <nPedRegEvento> e o atributo Id tinha 59 caracteres onde o schema exige 62. Na prática, nenhum cancelamento era aceito pelo Ambiente Nacional. Corrigido.
Além do e101101 (cancelamento), agora também é gerado o corpo do e105102 (cancelamento por substituição), que antes tinha o código mapeado mas produzia um infPedReg sem o grupo do evento.
$std->infPedReg->e101101 = (object) [ 'cMotivo' => '1', // TSCodJustCanc: 1, 2 ou 9 'xMotivo' => 'Erro na emissao da nota fiscal', ]; // Substituição usa outra enumeração — com zero à esquerda: $std->infPedReg->e105102 = (object) [ 'cMotivo' => '01', // TSCodJustSubst: 01..05, 99 'chSubstituta' => '...', // chave da NFS-e substituta ];
O xDesc não precisa ser informado — é enumeração de valor fixo no schema e o pacote o deriva do próprio evento (a grafia do 105102, por exemplo, não leva cedilha). O nPedRegEvento assume 1 por padrão; informe-o em $std->infPedReg->nPedRegEvento nos eventos que podem se repetir.
🛡️ Compatibilidade de versão do PHP
generateId() e o cliente HTTP usavam mb_str_pad() e mb_trim(), funções exclusivas do PHP 8.4, enquanto o pacote declara ^8.1. Em PHP 8.1–8.3 a instalação passava sem aviso e o pacote quebrava na primeira emissão. Trocadas por str_pad()/trim().
A regra
mb_str_functionsdo.php-cs-fixer.phpfoi desligada de propósito: ela reconvertia essas chamadas automaticamente, reintroduzindo o bug a cada formatação.
🧹 Limpeza e correções
cNBSé obrigatório no schema e era emitido sobisset(), como se fosse opcional — a nota saía sem ele e a SEFAZ rejeitava comL2103.- Remoção de código morto (incluindo um elemento
<DPS>órfão criado dentro do gerador de eventos) e de umdump()que vazava no output em produção. - Correção de recursão sem
returnna geração de nomes de arquivos temporários de certificado.
Instalação
Este pacote é distribuído via Composer:
composer require quantumtecnology/nfse-nacional
Requisitos
- PHP 8.1+
- ext-dom, ext-curl, ext-zlib, ext-openssl, ext-mbstring
Testes
composer install composer test # ou: vendor/bin/phpunit
A suíte valida o XML gerado contra os XSDs oficiais v1.01 (storage/schemes/) e inclui um teste de paridade com uma NFS-e real autorizada pelo Ambiente Nacional (fixture anonimizada em tests/Fixtures/).
Serviços implementados
| Método | Descrição | Exemplo |
|---|---|---|
enviaDps |
Emite a NFS-e (envia o DPS) | MakeDps.php |
enviaDps com IBS/CBS |
Emissão com o destaque da Reforma Tributária | MakeDpsIbsCbs.php |
cancelaNfse (e101101) |
Cancela uma NFS-e autorizada | CancelaNfse.php |
cancelaNfse (e105102) |
Cancelamento por substituição | SubstituiNfse.php |
consultarNfseChave |
Consulta a NFS-e pela chave (XML) | ConsultaNfseChave.php |
consultarDpsChave |
Consulta o DPS pela chave | ConsultaDpsChave.php |
consultarNfseEventos |
Consulta eventos de uma NFS-e | ConsultaNfseEventos.php |
consultarDanfse |
Baixa a DANFSe (PDF oficial) do ADN | ConsultaDanfse.php |
Danfse / DanfseSimples |
DANFSe oficial com fallback local quando o ADN falha | GeraDanfseLocal.php |
Dps::validate() / getErrors() |
Valida o XML antes de transmitir | MakeDpsIbsCbs.php |
Todos os exemplos estão em examples/ e rodam em PHP puro — basta ajustar o certificado e os dados do emitente.
⚠️ Avisos importantes
DANFSe: o endpoint do ADN é intermitente
consultarDanfse() pode voltar vazio para uma chave válida — indisponibilidade temporária do Ambiente Nacional, não "nota inexistente". Em produção, trate assim:
- Repita algumas vezes antes de desistir (com espera progressiva);
- Confira a assinatura do PDF (
str_starts_with($retorno, '%PDF')) — o endpoint também devolve JSON de erro; - Não repita em caso de exceção (certificado, config, rede): é falha dura;
- Caia para a geração local a partir do XML.
🔑 A chave precisa ser numérica, 50 dígitos. A SEFAZ devolve
chaveAcessocom o prefixoNFS(decoração doIddo XML) e, com ele, a consulta volta vazia sem erro — um dos enganos mais comuns. Limpe compreg_replace('/\D/', '', $chave).
A implementação completa está em GeraDanfseLocal.php.
Configuração da prefeitura
A variável prefeitura aceita atualmente dois formatos:
- Um identificador textual, por exemplo:
americana-sp - O código IBGE do município (ex.:
3501608)
⚠️ Ambos são aceitos por compatibilidade, mas o padrão futuro será exclusivamente o código IBGE. Recomenda-se já adotá-lo. As URLs e operações por município ficam em storage/prefeituras.json.
Estrutura do prefeituras.json
Cada município tem urls (a base por ambiente) e operations (o caminho de cada operação). A URL final é montada como base + "/" + operação (a operação vazia "" usa a base direta):
{
"3501608": { // código IBGE (chave recomendada)
"urls": {
"sefin_producao": "https://nfse.americana.sp.gov.br/api/adn/dps",
"sefin_homologacao": "https://americanahomologacao.nfe.com.br/api/adn/dps"
},
"operations": {
"emitir_nfse": "recepcao", // => .../api/adn/dps/recepcao
"cancelar_nfse": "evento" // => .../api/adn/dps/evento
}
}
}
⚠️ Não deixe
cancelar_nfsevazio quando o cancelamento usa um caminho diferente da emissão. Se a base apontar para.../recepcaoecancelar_nfsefor"", o evento de cancelamento será postado em.../recepcaoe a prefeitura responderá "DPS inválido ou não informado". Prefira manter a base no nível comum (.../api/adn/dps) e especificar cada operação. Consulte o manual da prefeitura para os endpoints corretos (emissão, evento/cancelamento, consulta por chave, download de XML/DANFSe).
consultarNfseChave() e encoding
O XML, após o gzdecode, vem em ISO-8859-1. Por padrão o método mantém ISO via mb_convert_encoding. Caso tenha problemas, passe false no segundo parâmetro para receber o XML cru:
// Retorna ISO-8859-1 (padrão) $tools->consultarNfseChave('CHAVE_NFSE'); // Retorna o XML cru, sem mb_convert_encoding $tools->consultarNfseChave('CHAVE_NFSE', false);
FAQ — E999 (erro não catalogado)
Esse erro se refere a uma falha não catalogada pela própria Receita, incluindo erros de servidor (500) e problemas aleatórios. No ambiente de homologação costuma aparecer sem motivo aparente, enquanto em produção a nota normalmente é emitida sem problemas.
Causa mais comum relatada:
- CPF/CNPJ do prestador não existente / não cadastrado / não habilitado na NFS-e Nacional ou na prefeitura.
Créditos
Este fork é mantido pela Quantum Tecnology, mas não nasceu do zero. Ele se apoia inteiramente no trabalho de quem veio antes.
Nosso agradecimento à equipe Hadder e em especial ao Fernando Friedrich (Rainzart/nfse-nacional) por criar, manter e disponibilizar este pacote como Open Source. As melhorias desta versão só foram possíveis porque havia uma base sólida e bem estruturada para construir em cima.
A seguir, o agradecimento original do autor — que mantemos na íntegra:
(por Fernando Friedrich)
Este pacote não caiu do céu, não apareceu por geração espontânea e muito menos foi escrito do zero em um surto de genialidade de minha parte.
Ele foi copiado, clonado, analisado, desmontado, reaproveitado, adaptado e por fim ajustado por mim, tendo como base pacotes de emissão de NFSe que eram disponibilizados como Open Source pelo Sr. Roberto L. Machado e que, atualmente, não se encontram mais disponíveis publicamente.
Sim, variáveis, métodos, classes, estruturas e ideias de arquitetura foram utilizadas como referência (copiadas) — algumas foram alteradas, outras melhoradas, outras apenas sobreviveram ao tempo — sempre tendo como principal base o projeto NFePHP.
Na época da criação deste repositório, o cenário era simples: eu precisava emitir notas fiscais para meus clientes. Não existia nenhuma alternativa Open Source ativa e funcional em PHP, e depender de APIs pagas definitivamente não era uma opção para mim.
Diante disso, fica aqui meu agradecimento mais do que merecido ao Roberto, por criar, manter e disponibilizar gratuitamente projetos como o NFePHP.
Sem esse trabalho prévio, este repositório muito provavelmente não existiria.
E, por fim, obrigado a todas as pessoas que contribuem com este projeto — enviando PRs, sugerindo melhorias, corrigindo bugs ou apontando problemas. A lista de contribuidores do projeto original pode ser vista em: https://github.com/Rainzart/nfse-nacional/graphs/contributors
Licença
MIT.