quantumtecnology/nfse-nacional

Pacote para geração de NFSe Nacional usando componentes NFePHP (https://github.com/nfephp-org)

Maintainers

Package info

github.com/Quantum-Tecnology/nfse-nacional

pkg:composer/quantumtecnology/nfse-nacional

Transparency log

Statistics

Installs: 440

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 1

3.2.4 2026-07-31 21:15 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áriaCST 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, cIndOp e cCredPres têm zero à esquerda significativo — um cast para int transforma '000001' em 1 e 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/recepcao e o cancelamento (evento) para .../api/adn/dps/evento. Por isso o storage/prefeituras.json separa urls (base) de operations (caminho por operação): a URL final é base + "/" + operação. Se cancelar_nfse ficar 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

  • cTribNac obrigatório (6 dígitos) é validado na montagem do DPS. Antes, um valor ausente gerava uma tag <cTribNac/> vazia e a SEFAZ rejeitava com L2103 (XML fora do schema) — erro difícil de diagnosticar. Agora você recebe uma InvalidArgumentException clara, 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\... por use 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_functions do .php-cs-fixer.php foi 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 sob isset(), como se fosse opcional — a nota saía sem ele e a SEFAZ rejeitava com L2103.
  • Remoção de código morto (incluindo um elemento <DPS> órfão criado dentro do gerador de eventos) e de um dump() que vazava no output em produção.
  • Correção de recursão sem return na 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:

  1. Repita algumas vezes antes de desistir (com espera progressiva);
  2. Confira a assinatura do PDF (str_starts_with($retorno, '%PDF')) — o endpoint também devolve JSON de erro;
  3. Não repita em caso de exceção (certificado, config, rede): é falha dura;
  4. Caia para a geração local a partir do XML.

🔑 A chave precisa ser numérica, 50 dígitos. A SEFAZ devolve chaveAcesso com o prefixo NFS (decoração do Id do XML) e, com ele, a consulta volta vazia sem erro — um dos enganos mais comuns. Limpe com preg_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_nfse vazio quando o cancelamento usa um caminho diferente da emissão. Se a base apontar para .../recepcao e cancelar_nfse for "", o evento de cancelamento será postado em .../recepcao e 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.