SDK PHP framework-agnostic para integração com NFS-e Nacional (Padrão Brasileiro SEFIN). Funciona em Laravel, Symfony, projeto vanilla — qualquer coisa com PHP 8.1+ e suporte a PSR-3/PSR-18.
📖 Documentação:
- Este README — quickstart, instalação, exemplos rápidos
- MANUAL.md — referência completa da API (cada método: assinatura, parâmetros, retorno, exceções)
- CHANGELOG.md — histórico de versões
examples/— scripts ponta-a-ponta (emitir, cancelar, substituir, consultar, download, danfse-local)
Ciclo de vida da NFS-e completo: emissão, consulta, cancelamento, substituição,
manifestação, download (com retry), DANFSe NT 008/2026 e Distribuição de DFe
(caixa postal do CNPJ). Cobertura ampla do DPS conforme leiaute V1.00.02:
Intermediário (<interm>), Informações Complementares (<infoCompl>),
Deduções com documentos referenciados (<docDedRed>),
PIS/COFINS e retenções federais (<tribFed>), <tribMun> completo
(imunidade, exportação, benefício municipal, exigibilidade suspensa).
XSDs oficiais versionados em docs/schemas/. PHPStan level 8 limpo,
336 testes verdes, validado ponta-a-ponta em homologação SEFIN. Pré-1.0 —
API pode sofrer ajustes minor antes do 1.0.0; ver CHANGELOG.
- Suporte completo ao leiaute SefinNacional 1.6 / V1.00.02 do Sistema Nacional NFS-e
- Suporte à NT 008/2026 (novo DANFSe válido a partir de 1º/jul/2026)
- Cobertura tributária ampla: imunidade, exportação, benefício municipal, exigibilidade suspensa, dispensa de ISSQN para MEI
- Tabelas oficiais como enums tipados:
ListaServicosNacional(335 cases da LC 116/2003) eListaNbs(917 cases) - Sem dependência de framework — funciona em Laravel, Symfony, projetos vanilla
- Intermediário (
<interm>) — marketplaces, plataformas de delivery, agências de turismo - Deduções com documentos (
<docDedRed>) — construção civil (materiais/subempreitada), repasses, reembolsos - PIS/COFINS + retenções federais (
<tribFed>) — CST configurável- retenções de IRRF/CP/CSLL
- Distribuição de DFe paginada (caixa postal do CNPJ no SEFIN), com
helpers
chavesCanceladas,statusPorChave,agruparPorChave, etc. - Detecção de cancelamento via
$nfse->verificarCancelamento($chave)(forma canônica —consultar()retorna cStat=100 mesmo após cancelar) - Retry automático com backoff exponencial no download de DANFSe (502/503/504)
- Verificação idempotente de DPS (
HEAD /dps/{id}) antes de emitir - Tipagem forte (PHPStan level 8)
- Comércio exterior (
<comExt>) — exportação de serviços + códigos BACEN de moeda + mecanismos de fomento PROEX/BNDES-Exim - Construção civil (
<obra>) — CNO/CIB ou endereço - Eventos (
<atvEvento>) — shows, conferências, etc. - Endereço internacional (
<endExt>) em Tomador e Intermediário — exportação, marketplace global, tomador estrangeiro - Testes desde o dia 1 — 336 testes verdes em CI (PHP 8.1–8.5)
- PHP ^8.1
- Extensões:
dom,openssl,libxml,zlib,mbstring - Certificado digital A1 (.pfx) do prestador
- OpenSSL com legacy provider habilitado (rsa-sha1) — ver Configuração OpenSSL
composer require mendesalexandre/php-nfse-nacionaluse PhpNfseNacional\NFSe;
use PhpNfseNacional\Config;
use PhpNfseNacional\Certificate\Certificate;
use PhpNfseNacional\DTO\{Prestador, Tomador, Endereco, Servico, Valores, Identificacao};
use PhpNfseNacional\Enums\{Ambiente, RegimeEspecialTributacao};
// 1. Carregue o certificado A1
$cert = Certificate::fromPfxFile('/path/cert.pfx', 'senha-do-pfx');
// 2. Configure o prestador (singleton, uma vez na app)
$prestador = new Prestador(
cnpj: '12345678000195',
inscricaoMunicipal: '12345',
razaoSocial: 'EMPRESA XYZ',
endereco: new Endereco(
logradouro: 'RUA EXEMPLO',
numero: '100',
bairro: 'CENTRO',
cep: '01310100',
codigoMunicipioIbge: '3550308',
uf: 'MT',
),
regimeEspecial: RegimeEspecialTributacao::NotarioOuRegistrador,
);
$config = new Config(
prestador: $prestador,
ambiente: Ambiente::Homologacao,
);
// 3. Crie o SDK
$nfse = NFSe::create($config, $cert);
// 4. Emita uma NFS-e
$resposta = $nfse->emitir(
identificacao: new Identificacao(numeroDps: 1, serie: '1'),
tomador: new Tomador(
documento: '12345678901',
nome: 'Cliente Exemplo',
endereco: new Endereco(
logradouro: 'Rua A',
numero: '100',
bairro: 'Centro',
cep: '01310100',
codigoMunicipioIbge: '3550308',
uf: 'MT',
),
),
servico: new Servico(
discriminacao: 'Certidão de matrícula',
codigoMunicipioPrestacao: '3550308',
),
valores: new Valores(
valorServicos: 100.00,
deducoesReducoes: 20.00,
aliquotaIssqnPercentual: 4.00,
),
);
echo "Chave: " . $resposta->chaveAcesso;
echo "Número: " . $resposta->numeroNfse;use PhpNfseNacional\DTO\MotivoCancelamento;
$resposta = $nfse->cancelar(
chaveAcesso: '35012345200001234567890123456789012345678123456789',
motivo: MotivoCancelamento::ErroEmissao,
justificativa: 'Valor da NFS-e divergente do recibo',
);MEI (dispensa do ISSQN apurado):
use PhpNfseNacional\DTO\Valores;
use PhpNfseNacional\Enums\{SituacaoSimplesNacional, MotivoDispensaIssqn};
$prestador = new Prestador(
/* ... */
simplesNacional: SituacaoSimplesNacional::MEI,
inscricaoMunicipal: null, // MEI normalmente sem IM cadastrada
);
$valores = new Valores(
valorServicos: 800.00,
deducoesReducoes: 0.00,
aliquotaIssqnPercentual: 0.00,
motivoDispensaIssqn: MotivoDispensaIssqn::OptanteSimplesNacional,
// emite <indTotTrib>0</indTotTrib> em vez de <pTotTrib>
);Tomador estrangeiro (exportação de serviço):
use PhpNfseNacional\DTO\{Tomador, EnderecoExterior};
$tomador = new Tomador(
documento: '12345678901', // ou NIF estrangeiro (futuro)
nome: 'JOHN DOE',
endereco: new EnderecoExterior(
logradouro: '5th Avenue',
numero: '350',
bairro: 'Manhattan',
codigoPaisIso: 'US', // 2 letras ISO
codigoEnderecamentoPostal: '10118',
cidade: 'New York',
estadoProvinciaRegiao: 'NY',
),
);
// DpsBuilder detecta o tipo e emite <endExt> em vez de <endNac>Retenção do ISSQN (3 estados):
use PhpNfseNacional\Enums\TipoRetencaoIssqn;
$valores = new Valores(
valorServicos: 1000.00,
deducoesReducoes: 0.00,
aliquotaIssqnPercentual: 4.00,
tipoRetencaoIssqn: TipoRetencaoIssqn::RetidoPeloTomador, // tomador retém ISSQN
// alternativas: NaoRetido (default), RetidoPeloIntermediario
);Imunidade (templos, partidos, livros, etc.):
use PhpNfseNacional\Enums\{TipoTributacaoIssqn, TipoImunidadeIssqn};
$valores = new Valores(
valorServicos: 100.00,
deducoesReducoes: 0.00,
aliquotaIssqnPercentual: 0.00,
tributacaoIssqn: TipoTributacaoIssqn::Imunidade,
imunidade: TipoImunidadeIssqn::TemplosQualquerCulto,
);Benefício Municipal (redução de BC concedida pelo município):
use PhpNfseNacional\DTO\BeneficioMunicipal;
$valores = new Valores(
valorServicos: 100.00,
deducoesReducoes: 0.00,
aliquotaIssqnPercentual: 4.00,
beneficioMunicipal: new BeneficioMunicipal(
nBM: '35503080100001', // ID parametrizado pelo município
percentualReducaoBc: 50.00, // ou valorReducaoBc — choice no schema
),
);Códigos de serviço via enum (LC 116):
use PhpNfseNacional\Enums\{ListaServicosNacional, ListaNbs};
$servico = new Servico(
discriminacao: 'Análise e desenvolvimento de sistemas',
codigoMunicipioPrestacao: '3550308',
cTribNac: ListaServicosNacional::S010101, // ou string '010101'
cNBS: ListaNbs::N101011100, // ou string '101011100'
);
echo $servico->cTribNac; // '010101'
echo ListaServicosNacional::S010101->descricao(); // 'Análise e desenvolvimento...'Intermediário (marketplace / plataforma):
use PhpNfseNacional\DTO\Intermediario;
$intermediario = new Intermediario(
documento: '12345678000195',
nome: 'MARKETPLACE EXEMPLO LTDA',
endereco: $endereco, // Endereco nacional OU EnderecoExterior — opcional
inscricaoMunicipal: '987654',
email: 'fiscal@marketplace.com',
);
$nfse->emitir(
identificacao: $id,
tomador: $tomador,
servico: $servico,
valores: $valores,
intermediario: $intermediario,
);Informações complementares na NFS-e:
use PhpNfseNacional\DTO\InformacoesComplementares;
$servico = new Servico(
discriminacao: 'Serviço prestado',
codigoMunicipioPrestacao: '...',
infoCompl: new InformacoesComplementares(
xInfComp: 'Pedido #12345 - pagamento à vista - cliente VIP',
),
);Deduções com documentos (construção civil, repasses):
use PhpNfseNacional\DTO\DocumentoDeducao;
use PhpNfseNacional\Enums\TipoDeducaoReducao;
$deducao = new DocumentoDeducao(
tipo: TipoDeducaoReducao::Materiais,
dataEmissaoDocumento: new DateTimeImmutable('2026-05-01'),
valorDedutivel: 5000.00,
valorDeducao: 4500.00,
chaveNfe: '35012345...', // ou chaveNfse / numeroDocumento
);
$valores = new Valores(
valorServicos: 10000.00,
deducoesReducoes: 0.00, // ZERO! choice no schema
aliquotaIssqnPercentual: 4.00,
documentosDeducao: [$deducao],
);PIS/COFINS retido + IRRF/CP/CSLL:
use PhpNfseNacional\DTO\TributacaoPisCofins;
use PhpNfseNacional\Enums\{CstPisCofins, TipoRetencaoPisCofins};
$valores = new Valores(
valorServicos: 1000.00,
deducoesReducoes: 0.00,
aliquotaIssqnPercentual: 4.00,
tributacaoPisCofins: new TributacaoPisCofins(
cst: CstPisCofins::OperacaoTributavelAliquotaBasica,
valorBaseCalculo: 1000.00,
aliquotaPis: 0.65,
aliquotaCofins: 3.00,
valorPis: 6.50,
valorCofins: 30.00,
tipoRetencao: TipoRetencaoPisCofins::Retido,
),
valorRetidoIrrf: 15.00,
valorRetidoCp: 11.00,
valorRetidoCsll: 10.00,
);Pra evitar dupla emissão (sequencial DPS reutilizado, retry agressivo do
cliente, etc.), use verificarDps() antes de chamar emitir():
$idDps = 'DPS35503082212345678000195000010000000001285AB';
if ($nfse->verificarDps($idDps)) {
echo "DPS já existe no SEFIN — não emite de novo\n";
} else {
$resposta = $nfse->emitir(...);
}Usa HEAD /dps/{id} — leve, sem baixar o corpo.
Quando alguém emite uma NFS-e contra o seu CNPJ (como tomador), o SEFIN
guarda numa "caixa postal" associada ao CNPJ. O método sincronizarDfe()
itera por todos os DFes desde o último NSU conhecido:
// Persiste o último NSU pra sincronização incremental
$ultimoNsuConhecido = (int) $cache->get('dfe_ultimo_nsu', 0);
$resp = $nfse->sincronizarDfe($ultimoNsuConhecido);
foreach ($resp->itens as $item) {
echo "NSU {$item->nsu}: {$item->tipoDocumento} chave={$item->chaveAcesso}\n";
}
$cache->set('dfe_ultimo_nsu', $resp->ultimoNsu);O loop interno paginado pega até 20 páginas (= até 1000 DFes) por chamada.
Para auditoria de eventos vinculados a uma NFS-e específica, use
listarEventos($chave).
Cancela uma NFS-e e a vincula a uma substituidora previamente emitida.
A substituidora precisa ter sido emitida antes via $nfse->emitir(...).
use PhpNfseNacional\DTO\MotivoCancelamento;
use PhpNfseNacional\DTO\MotivoSubstituicao;
$resposta = $nfse->substituir(
chaveOriginal: '35503082212345678000195000000000005712345678901234',
chaveSubstituta: '35503082212345678000195000000000005812345678901235',
motivo: MotivoSubstituicao::DesenquadramentoSimples,
);Pra outros tipos de evento que aparecerem no leiaute, implemente a interface
EventoNfse e use o EventoBuilder diretamente — sem alterar o SDK:
use PhpNfseNacional\Dps\EventoNfse;
use PhpNfseNacional\Dps\EventoBuilder;
final class MeuEventoCustomizado implements EventoNfse
{
public function chaveAcesso(): string { return '...'; }
public function codigoTipoEvento(): string { return '101102'; } // ex: substituição
public function nSequencial(): int { return 1; }
public function descricao(): string { return 'Substituição de NFS-e'; }
public function camposGrupo(): array { return ['campoX' => 'valor']; }
}
$xml = (new EventoBuilder($config))->build(new MeuEventoCustomizado());
$xmlAssinado = $signer->sign($xml, 'infPedReg');
$resposta = $client->postXml($endpoints->cancelarNfse($chave), $xmlAssinado);OpenSSL 3.5+ (Fedora 43, RHEL 9) desabilita SHA1 por padrão. A DPS do
SEFIN usa rsa-sha1 — sem habilitar legacy, openssl_sign falha com
error:03000098:digital envelope routines::invalid digest.
Opção 1 (recomendada em prod): env var
Criar /etc/ssl/openssl-sha1.cnf:
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
[provider_sect]
default = default_sect
legacy = legacy_sect
[default_sect]
activate = 1
[legacy_sect]
activate = 1Setar no Supervisor/php-fpm:
environment=OPENSSL_CONF=/etc/ssl/openssl-sha1.cnf
Opção 2 (dev/local): runtime
use PhpNfseNacional\Certificate\Signer;
Signer::habilitarLegacyProviderRuntime();src/
├── NFSe.php # Facade unificado (entry point)
├── Config.php # Config imutável
├── DTO/ # Dados imutáveis readonly
├── Enums/ # Ambiente, RegimeEspecial, etc.
├── Certificate/ # Carga .pfx + rsa-sha1
├── Dps/ # DpsBuilder + EventoCancelamentoBuilder
├── Sefin/ # SefinClient (HTTP), Endpoints, Resposta
├── Services/ # Emissão, Consulta, Cancelamento, Download
├── Danfse/ # DANFSE NT 008 (parser + layout + generator)
├── Exceptions/
└── Support/ # Documento, TextoSanitizador
- Estrutura + composer + DTOs + Config
- Certificate + Signer rsa-sha1 (com fallback SAN OID 2.16.76.1.3.3)
- DpsBuilder com cobertura completa
<tribMun>(BM/exigSusp/imunidade) - SefinClient + EmissaoService
- ConsultaService (status NFS-e, eventos)
- CancelamentoService (e101101)
- DownloadService (XML + PDF + retry exponencial no DANFSe)
- DANFSe PDF local — NT 008/2026 (TCPDF + QR Code)
- DANFSe customizável (logo do prestador, observações livres)
- Substituição de NFS-e (evento e105102)
- Manifestação (Confirmar / Rejeitar / Anular Rejeição)
- DFe paginado (
GET /contribuintes/DFe/{NSU}) — caixa postal do CNPJ - Listagem de eventos por NFS-e (
GET /contribuintes/NFSe/{chave}/Eventos) - Verificação idempotente de DPS (
HEAD /dps/{id}) - Enums das tabelas oficiais:
ListaServicosNacional(335),ListaNbs(917),TipoBeneficioMunicipal,TipoImunidadeIssqn,TipoExigibilidadeSuspensa,RegimeApuracaoSimplesNacional,TipoRetencaoIssqn,TipoTributacaoIssqn - PHPStan level 8 limpo, 336 testes unitários
- CI no GitHub Actions (PHP 8.1 – 8.5)
- Validação ponta-a-ponta em homologação SEFIN
- Examples completos do ciclo de vida
-
<interm>(Intermediário) — v0.12.0 -
<serv/infoCompl>(Informações Complementares) — v0.13.0 -
<vDedRed/documentos>/<docDedRed>(Deduções com docs referenciados) — v0.13.0 -
<trib/tribFed>/<piscofins>+ retenções federais — v0.13.0 -
<BM>+<exigSusp>+<tpImunidade>dentro de<tribMun>— v0.10.0 - XSDs oficiais versionados em
docs/schemas/— v0.12.0 - BC-break v0.14.0:
Valores::$issqnRetido(bool) →tipoRetencaoIssqn(enum 3 estados) +$dispensadoIssqn(bool) →$motivoDispensaIssqn(enum 4 cases) - Onda 5:
<comExt>(exportação),<obra>(construção civil),<atvEvento>(eventos) — v0.15.0.<explRod>e<lsadppu>fora-de-escopo (removidos do leiaute entre v1.00 e v1.01) - Endereço internacional (
endExt) em Tomador e Intermediário — v0.16.0 - Grupo
<IBSCBS>configurável (Reforma Tributária) —Valores::$cstIbsCbs/$cClassTrib(v0.21.0),$cCredPres/$tribRegular(v0.25.0). Opt-in viaConfig::incluirIbsCbs -
Servico::$cTribMun(código de tributação municipal do ISSQN, opcional) — v0.24.0 - DANFSe local — bloco "Canhoto" (item 2.1.13, Anexo I) —
DanfseCustomizacao::$canhoto(EmBranco|PreenchidoAutomaticamente) — v0.26.0 - DANFSe local — 15 fixes de leiaute V2 (canhoto sem overflow, moldura sem gap, auto-fit de fonte com truncamento pra nomes/chaves longas, Ambiente Gerador correto, descrição de serviço com altura dinâmica, entre outros) — v0.26.1 a v0.26.10
- Endereço internacional em Prestador (caso raro: prestador estrangeiro)
- Grupo
<fornec>dentro de<docDedRed>(fornecedor do documento de dedução)
MIT