Download the PHP package ownerpro/nfsen-php-sdk without Composer
On this page you can find all versions of the php package ownerpro/nfsen-php-sdk. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download ownerpro/nfsen-php-sdk
More information about ownerpro/nfsen-php-sdk
Files in ownerpro/nfsen-php-sdk
Package nfsen-php-sdk
Short Description SDK PHP para emissão de NFS-e Nacional — Laravel ou standalone (sem framework)
License MIT
Homepage https://github.com/OwnerPro-Software/nfsen-php-sdk
Informations about the package nfsen-php-sdk
NFSe Nacional
Pacote PHP para emissão, cancelamento, substituição e consulta de NFSe Padrão Nacional (nfse.gov.br) via API REST. Funciona com Laravel 11/12/13 ou standalone (sem framework).
Funcionalidades
- Emissão de NFSe (
emitir) - Cancelamento de NFSe (
cancelar) e solicitação de análise fiscal quando o prazo do município expirou (solicitarAnaliseFiscalCancelamento) - Substituição de NFSe (
substituir) - Consulta por chave de acesso, DPS, eventos e verificação de DPS
- Distribuição de documentos fiscais via ADN — consulta em lote por NSU (
distribuicao) - Assinatura digital XML com certificado A1 (PFX/P12)
- Validação XSD dos documentos
- Geração local do DANFSe (PDF/HTML) em conformidade com a NT 008 — DANFSe v2.0, com os blocos de Destinatário e IBS/CBS da reforma tributária
- Eventos Laravel opcionais (
NfseEmitted,NfseCancelled,NfseRejected, etc.) - mTLS sem escrita nomeada em disco
- 100% de cobertura de testes e tipos
Requisitos
- PHP 8.3+
- Extensões:
curl,dom,zlib,openssl,mbstring,libxml - Laravel 11, 12 ou 13 (opcional — funciona standalone)
Instalação
Configuração
Laravel
Publique o arquivo de configuração:
Adicione as variáveis de ambiente no .env:
A variavel NFSE_VALIDATE_IDENTITY (padrao: true) controla se o SDK verifica que o CNPJ ou CPF do certificado digital corresponde ao prestador informado na DPS antes de enviar a requisicao. Desative (false) apenas quando um representante legal (contador ou procurador com procuracao eletronica) emite notas em nome de terceiros.
Standalone (sem Laravel)
A validação de identidade confere o certificado contra o emitente da DPS, que
infDPS/tpEmit designa — o prestador (1), o tomador (2) ou o intermediário (3).
Com tpEmit 2 ou 3, quem assina é o tomador ou o intermediário, e é o CNPJ/CPF
dele que precisa bater. Certificado e emitente que se identificam por documentos
de tipos diferentes (e-CPF contra emitente que só declara CNPJ, ou o inverso)
também são recusados: não há como conferir se são a mesma pessoa.
Para desabilitar a validacao de identidade (representante legal / contador):
Uso
Emitir NFSe
Emitir NFSe por decisão judicial — não suportado
emitirDecisaoJudicial() existe na interface, mas lança NfseException na entrada, antes
de qualquer evento ou requisição. A operação não é implementável a partir de uma DPS:
O decisao-judicial/nfse recebe o documento NFS-e completo, não a DPS — o corpo é
NFSeBypassPostRequest.xmlGZipB64, descrito no SefinNacional-swagger.json como "Documento
XML da NFSe compactado no padrão GZIP". Em TCInfNFSe a DPS é apenas o último filho, ao
lado de campos que só o gerador da nota preenche — nNFSe, nDFSe, cStat (102 = NFS-e de
Decisão Judicial), dhProc e os valores já apurados (vBC, pAliqAplic, vISSQN). O
ambGer (TSAmbGeradorNFSe) admite apenas 1-Prefeitura e 2-Sistema Nacional da NFS-e:
não há valor para contribuinte, e tpEmis 2 descreve "emissão original em leiaute próprio do
município com transcrição para o modelo nacional".
Emitir por decisão judicial cabe, portanto, a quem gera a NFS-e. Se você detém essa
autorização e já monta o XML da nota, a chamada é um POST direto ao endpoint — este SDK não
constrói nem assina documentos NFSe.
Cancelar NFSe
Codigos de cancelamento: ErroEmissao, ServicoNaoPrestado, Outros.
Solicitar análise fiscal para cancelamento
Cada município parametriza o prazo de cancelamento direto. Passado esse prazo, a SEFIN
rejeita cancelar() com o código E0822 ("O prazo para o cancelamento da NFS-e expirou,
conforme parametrização do município emissor da NFS-e") e o único caminho é pedir análise
fiscal — o evento e101103, mesmo fluxo que o portal nacional oferece.
Os códigos de motivo são os mesmos do cancelamento: ErroEmissao, ServicoNaoPrestado,
Outros.
Sucesso aqui significa pedido registrado, não nota cancelada: a NFS-e segue válida até
o fisco decidir. O SDK dispara NfseFiscalAnalysisRequested, nunca NfseCancelled.
Acompanhar a análise
TStat (situação da NFS-e) não tem estado para análise em curso — a nota continua
"Gerada" o tempo todo. O que muda são os eventos vinculados à chave, e
situacaoCancelamento() os resume numa chamada ao ADN:
Vence o evento de maior NSU, então um pedido novo depois de um indeferimento volta a
reportar EmAnalise. Consulta rejeitada pelo ADN lança NfseException: rejeição não é
situação da nota.
Se você já consome distribuicao()->documentos($nsu), os eventos 105104/105105 chegam
nesse fluxo por NSU — o desfecho vem por lote, sem varrer nota a nota.
Substituir NFSe
O método substituir() emite uma DPS com o grupo subst preenchido automaticamente. O ADN (Ambiente de Dados Nacional) cancela a nota original ao processar a DPS substituta — uma única requisição.
Nota: O registro do evento de cancelamento por substituição (
e105102) via API Eventos (POST /nfse/{chave}/eventos) é restrito a sistemas municipais conveniados com o ADN — o autor desse evento é o município emissor (MEmis), não o contribuinte. O cancelamento da nota original ocorre automaticamente ao emitir a DPS com o gruposubstpreenchido.
Codigos de substituição: DesenquadramentoSimplesNacional, EnquadramentoSimplesNacional, InclusaoRetroativaImunidadeIsencao, ExclusaoRetroativaImunidadeIsencao, RejeicaoTomadorIntermediario, Outros.
Consultas
Gerar o ID da DPS sem montar o XML
O identificador de 45 posições da DPS (TSIdDPS) pode ser calculado fora da
emissão — útil para reconciliar após um timeout, consultando a DPS antes de
qualquer retry:
DpsId é a fonte única da regra fiscal de formação do ID — o mesmo código usado
internamente na construção do XML da DPS. O retorno é validado contra o padrão
DPS[0-9]{42} do schema nacional; entrada inválida lança InvalidDpsArgument.
cLocEmi, cnpj e cpf são conferidos na largura exata do schema (7, 14 e 11
dígitos, sem máscara): como entram no ID com largura fixa, um valor de tamanho
errado seria acomodado — cortado ou preenchido com zeros — e produziria um
identificador bem formado apontando para outro município ou outra inscrição.
Informe o mesmo CNPJ ou CPF usado na emissão — ambos null lança
InvalidDpsArgument (inscrição zerada só é válida para prestador estrangeiro
com NIF/cNaoNIF, via allowEmptyInscricao: true).
Reconciliação após resultado indeterminado na emissão
Reconciliação após resultado indeterminado no cancelamento
Mesma régua da emissão, ancorada na consulta de eventos:
Distribuição (ADN Contribuinte)
Consulta em lote de documentos fiscais via NSU (Número Sequencial Único) através do ADN (Ambiente de Dados Nacional). Útil para importação em massa de NFS-e.
Por padrão o cnpjConsulta vem do certificado. Com um e-CPF não há CNPJ a
enviar, e o parâmetro — opcional no contrato do ADN — é omitido da URL; informe-o
explicitamente se a consulta precisar dele.
O fluxo típico de importação:
- Comece com NSU
0 - Chame
documentos($nsu)— receba um lote - Guarde o maior NSU do lote
- Repita com o próximo NSU até
statusProcessamentoserNenhumDocumentoLocalizado
Laravel Facade
Eventos
O pacote dispara eventos Laravel que podem ser escutados na sua aplicação:
| Evento | Propriedades | Descricao |
|---|---|---|
NfseEmitted |
chave |
NFSe emitida com sucesso |
NfseCancelled |
chave |
NFSe cancelada com sucesso |
NfseFiscalAnalysisRequested |
chave |
Pedido de análise fiscal para cancelamento registrado (a NFS-e segue válida até o fisco decidir) |
NfseSubstituted |
chave, chaveSubstituta |
NFSe substituída com sucesso |
NfseQueried |
operacao |
Consulta realizada |
NfseRequested |
operacao, metadata |
Operação iniciada |
NfseRejected |
operacao, codigoErro, mensagemErro, correcao |
Operação rejeitada pela API (mensagemErro e correcao ficam null quando a API não retorna os campos correspondentes ou no fallback SEM_CHAVE) |
NfseFailed |
operacao, mensagem, throwable |
Falha na operação |
Substituição: como substituir delega ao emitir internamente, a sequência de eventos disparados é:
NfseRequested('emitir') → NfseEmitted → NfseSubstituted
Objetos de Resposta
Cada operação retorna um DTO tipado e imutável:
NfseResponse
Retornado por emitir(), cancelar(), substituir(), consultar()->nfse() e consultar()->dps().
| Propriedade | Tipo | Descricao |
|---|---|---|
sucesso |
bool |
Se a operação foi aceita |
chave |
?string |
Chave de acesso da NFSe (50 dígitos) |
xml |
?string |
XML da NFSe processada |
idDps |
?string |
Identificador da DPS |
alertas |
list<ProcessingMessage> |
Alertas não-bloqueantes |
erros |
list<ProcessingMessage> |
Erros de processamento |
tipoAmbiente |
?int |
1 = Produção, 2 = Homologação |
versaoAplicativo |
?string |
Versão do aplicativo da SEFIN |
dataHoraProcessamento |
?string |
Data/hora do processamento |
raw |
?array |
Corpo JSON decodificado, como a SEFIN o devolveu — ver Corpo bruto |
DanfseResponse
Retornado por consultar()->danfse().
| Propriedade | Tipo | Descricao |
|---|---|---|
sucesso |
bool |
Se o PDF foi obtido |
pdf |
?string |
Conteúdo binário do PDF |
erros |
list<ProcessingMessage> |
Erros de processamento |
A API remota de DANFSe foi sobrestada em 01/07/2026. A Nota Técnica nº 008, de 05/05/2026 (
storage/danfse/nt-008-se-cgnfse-danfse-20260505.pdf, seção 1), suspendeuhttps://adn.nfse.gov.br/danfsee transferiu a geração do documento para o emissor. Toda falha deconsultar()->danfse()passa a trazer, além do erro original, uma mensagem com o códigoDanfseResponse::API_SOBRESTADA.Gere o documento localmente a partir do XML da NFS-e:
EventsResponse
Retornado por consultar()->eventos().
| Propriedade | Tipo | Descricao |
|---|---|---|
sucesso |
bool |
Se a consulta teve sucesso |
xml |
?string |
XML do primeiro evento de eventos, já descomprimido |
erros |
list<ProcessingMessage> |
Erros de processamento |
tipoAmbiente |
?int |
1 = Produção, 2 = Homologação |
versaoAplicativo |
?string |
Versão do aplicativo da SEFIN |
dataHoraProcessamento |
?string |
Data/hora do processamento |
raw |
?array |
Corpo JSON decodificado, como a SEFIN o devolveu — ver Corpo bruto |
eventos |
list<EventoConsultado> |
Os eventos devolvidos para o par (tipoEvento, nSequencial) |
Constante EventsResponse::EVENT_NOT_FOUND: presente em erros[0]->codigo
quando a SEFIN responde 404 — o evento comprovadamente não existe (distinto de
erro transitório, que permanece sucesso: false sem esse código).
EventoConsultado
Um item de EventsResponse::$eventos.
| Propriedade | Tipo | Descricao |
|---|---|---|
chaveAcesso |
?string |
Chave de acesso da NFS-e a que o evento se vincula |
tipoEvento |
?TipoEvento |
null quando o código não consta no enum |
numeroPedidoRegistroEvento |
?int |
Sequencial do pedido de registro |
dataHoraRecebimento |
?string |
Quando a SEFIN recebeu o evento |
xml |
?string |
XML do evento, já descomprimido |
parseError |
?string |
Por que o evento não pôde ser interpretado por completo; null quando íntegro |
Um item que não pôde ser lido por completo não interrompe a resposta: os campos
afetados vêm null e parseError diz o que faltou.
DistribuicaoResponse
Retornado por distribuicao()->documentos(), distribuicao()->documento() e distribuicao()->eventos().
| Propriedade | Tipo | Descricao |
|---|---|---|
sucesso |
bool |
true quando statusProcessamento é DocumentosLocalizados |
statusProcessamento |
StatusDistribuicao |
Status: Rejeicao, NenhumDocumentoLocalizado, DocumentosLocalizados |
lote |
list<DocumentoFiscal> |
Documentos fiscais retornados |
alertas |
list<ProcessingMessage> |
Alertas não-bloqueantes |
erros |
list<ProcessingMessage> |
Erros de processamento |
tipoAmbiente |
?int |
1 = Produção, 2 = Homologação |
versaoAplicativo |
?string |
Versão do aplicativo |
dataHoraProcessamento |
?string |
Data/hora do processamento |
raw |
?array |
Corpo JSON decodificado, como o ADN o devolveu — ver Corpo bruto |
DocumentoFiscal
Cada item do lote na DistribuicaoResponse.
| Propriedade | Tipo | Descricao |
|---|---|---|
nsu |
?int |
Número Sequencial Único |
chaveAcesso |
?string |
Chave de acesso da NFS-e |
tipoDocumento |
?TipoDocumentoFiscal |
Tipo: Nfse, Dps, Evento, Cnc, PedidoRegistroEvento, Nenhum. null quando ausente ou desconhecido — veja parseError |
tipoEvento |
?TipoEventoDistribuicao |
Tipo do evento (quando tipoDocumento é Evento). null quando ausente ou desconhecido |
arquivoXml |
?string |
XML do documento (já descomprimido). null quando ausente ou indecodificável |
dataHoraGeracao |
?string |
Data/hora de geração |
parseError |
?string |
Por que o documento não pôde ser interpretado por completo; null quando íntegro. Nenhum campo de DistribuicaoNSU é obrigatório no contrato do ADN, e o governo pode emitir tipos que esta versão do SDK ainda não conhece — campo ausente ou com valor desconhecido entra no lote com aquele campo em null e o motivo aqui, em vez de derrubar o lote inteiro. O mesmo vale, por precaução, para valor fora do tipo que o swagger declara |
ProcessingMessage
Representa uma mensagem de erro ou alerta da API:
| Propriedade | Tipo | Descricao |
|---|---|---|
mensagem |
?string |
Mensagem principal |
codigo |
?string |
Código do erro/alerta |
descricao |
?string |
Descrição detalhada |
complemento |
?string |
Informação complementar |
parametros |
list<string> |
Parâmetros adicionais da mensagem |
Corpo bruto
NfseResponse, EventsResponse e DistribuicaoResponse carregam em raw o corpo JSON
decodificado, exatamente como a API o devolveu — em sucesso e em rejeição. É o que
sobrevive quando a normalização não alcança a resposta: envelope renomeado, campo novo,
mensagem em forma que o SDK ainda não conhece.
O conteúdo vem da API sem filtro nenhum: decida o que registrar antes de mandar para o
log. raw é [] quando o corpo não trouxe JSON legível (5xx de gateway, 204) e null
apenas em respostas construídas à mão. DanfseResponse não tem o campo — o corpo dela é
o PDF binário, que já está em pdf.
A SEFIN envia o envelope de erro em três formas — erros: [...], erro: {...} e
erro: [...] (lista, confirmada em produção com SefinNacional_1.6.0) —, e o SDK
normaliza as três nesta lista. Se a mensagem vier sem nenhuma chave conhecida, codigo
recebe a constante ProcessingMessage::FORMATO_DESCONHECIDO e complemento carrega o
item original em JSON, para que um formato novo da API não chegue vazio ao consumidor.
Exceções
| Exceção | Pai | Quando |
|---|---|---|
NfseException |
RuntimeException |
Erros gerais (XML inválido, falha de compressão, etc.) |
CommunicationException |
NfseException |
Base abstrata das falhas de comunicação (nenhuma resposta completa e legível). Capture-a para tratar tudo como indeterminado; capture as subclasses para distinguir |
IndeterminateResultException |
CommunicationException |
Resultado indeterminado: a requisição pode ou não ter sido processada pela SEFIN. Reconcilie antes de retry (veja abaixo). phase indica a fase da falha quando detectável (connect, dns, read, tls, transfer, body); é null quando a resposta chegou inteira e o que falta é evidência de processamento, como num 5xx sem rejeição da SEFIN |
RequestNotDeliveredException |
CommunicationException |
Não entregue: a falha ocorreu comprovadamente antes de qualquer byte HTTP ser enviado (phase: dns, connect ou tls). A operação não foi processada — retry direto é seguro, sem reconciliação |
HttpException |
NfseException |
Resposta HTTP de erro recebida sem corpo estruturado (redirect, 4xx vazio, 5xx em consulta; status inesperado em verificarDps()/dps()). Acesse getResponseBody() para detalhes. Num 5xx a operações que alteram estado (emitir, cancelar, substituir) o SDK lança IndeterminateResultException, não esta |
CertificateExpiredException |
NfseException |
Certificado PFX/P12 expirado (validTo no passado) |
CertificateNotYetValidException |
NfseException |
Certificado PFX/P12 com início de vigência no futuro (validFrom à frente do relógio) — renovação antecipada ou host com clock skew |
InvalidDpsArgument |
InvalidArgumentException |
Campos mutuamente exclusivos ou obrigatórios violados na DPS; ID de DPS fora do padrão TSIdDPS |
Contrato de indeterminação: capturar IndeterminateResultException
significa que a SEFIN pode ou não ter recebido e processado a requisição. Ela
cobre cinco situações:
- Falha antes de qualquer resposta (timeout, DNS, conexão recusada, TLS) — a requisição pode nem ter chegado ao servidor;
- Falha no meio da transferência (conexão resetada, corpo truncado) — o servidor processou, mas o resultado não pôde ser lido;
- Resposta 2xx com corpo ilegível (JSON inválido ou vazio) — o servidor confirmou o processamento, mas o resultado não pôde ser interpretado;
- Resposta com JSON válido porém sem o campo obrigatório da operação —
um 2xx de
consultar()->nfse()semnfseXmlGZipB64, deconsultar()->eventos()semeventosnemeventoXmlGZipB64, ou deconsultar()->dps()semchaveAcesso, ou a resposta ao POST do evento emcancelar()sem rejeição estruturada nem o reciboeventoXmlGZipB64, qualquer que seja o status — shape que não ocorre em operação normal; ausência comprovada é sinalizada por HTTP 404, nunca por corpo vazio; - Resposta 5xx a uma operação que altera estado (
emitir,cancelar,substituir) sem rejeição estruturada da SEFIN no corpo — o erro pode ter vindo de um proxy antes da SEFIN, ou da própria SEFIN depois de gravar a nota, e nada no corpo distingue os dois. Um 5xx que trazerros/erropreenchido é rejeição definitiva: prova que a requisição chegou e foi processada. Em consultas, 5xx continua lançandoHttpException— não há estado a reconciliar.
204 não entra nesta lista. "No Content" define corpo vazio, então a ausência de JSON ali é a resposta correta e não estado indeterminado —
distribuicao()devolvesucesso: falsecom o códigoEMPTY_RESPONSE.
A exceção carrega a resposta que a motivou, para que o diagnóstico saia do log em vez de exigir reproduzir a chamada:
| Propriedade | Tipo | Descricao |
|---|---|---|
phase |
?string |
Fase da falha de transporte (dns, connect, tls, transfer, read, body), quando detectável |
statusCode |
?int |
Status HTTP, quando a resposta chegou |
body |
?string |
Corpo cru, truncado em 8 KiB |
raw |
?array |
Corpo JSON decodificado, sem truncar |
Preenche-se o que o ponto da falha tinha em mãos: body/statusCode ficam null
nas rotas em que o SDK só recebe o JSON já decodificado, e raw fica null quando
não houve JSON legível (é justamente a causa) ou quando a falha antecedeu a
resposta. O mesmo objeto chega ao listener em NfseFailed::$throwable.
Nos cinco casos a ação é a mesma: nunca faça retry cego de emissão (a NFS-e
pode já existir e um retry causaria dupla emissão). Calcule o ID com
DpsId::generate() e consulte consultar()->dps($id): se encontrou, a nota
foi emitida; se retornar NfseResponse::DPS_NOT_FOUND, é seguro re-emitir com
o mesmo nDPS. Qualquer outra exceção ou resposta do SDK é uma resposta
definitiva do servidor.
Distinguindo "não entregue" de "indeterminado": falhas de DNS, conexão TCP e
handshake TLS acontecem antes de qualquer byte HTTP ser enviado — a
requisição comprovadamente não chegou à SEFIN e o retry direto é seguro, sem
reconciliação. Esses casos lançam RequestNotDeliveredException em vez de
IndeterminateResultException:
A classificação usa apenas o errno do cURL (evidência inequívoca: 6, 7, 35,
58, 60); qualquer ambiguidade — incluindo todo timeout (cURL 28, cuja fase
não é provável em conexões keep-alive reutilizadas) — permanece indeterminada.
Vale para todas as operações do SDK (emissão, cancelamento, consultas,
distribuição), não apenas emitir(). O default é false para não alterar
catches existentes de IndeterminateResultException;
catch (CommunicationException) cobre os dois tipos e equivale a tratar tudo
como indeterminado (sempre seguro).
Nota: com a flag ativa, um timeout de connect (cURL 28) reporta
phase: 'read'naIndeterminateResultException— a fase de um timeout não é provável por errno, então ele nunca vira "não entregue". Com a flag desativada, a fase legada vinda do texto da mensagem ('connect') é mantida. Não usephasepara decidir retry; ela é apenas diagnóstico.
Renderização local do DANFSE
O SDK gera o DANFSE (PDF ou HTML) localmente a partir do XML da NFS-e autorizada.
Este é o único caminho desde 01/07/2026. A Nota Técnica nº 008, de 05/05/2026, sobrestou a API de geração do DANFSe (
https://adn.nfse.gov.br/danfse) e transferiu a geração para o emissor.consultar()->danfse(), que chama aquele endpoint, passa a devolver falha com o códigoDanfseResponse::API_SOBRESTADA.
Uso básico
Sem customização
danfse() não recebe argumentos, e é de propósito. O layout inteiro vem da
NT 008 e o conteúdo, do XML da NFS-e — o item 2.1 é explícito: "não poderão
ser impressas informações que não constem do arquivo da NFS-e".
Isso vale também para as duas imagens que um DANFSe pode sugerir. A NT reserva um único
quadro para logomarca, no canto esquerdo do cabeçalho, e o item 2.4.3 diz de quem ele é:
da NFS-e, com o arquivo oficial indicado em gov.br. Ele vem embarcado no pacote
(storage/danfse/logo-nfse.png) e é sempre impresso. Não há quadro reservado à marca do
emitente nem à identificação da prefeitura em lugar nenhum do documento.
NFS-e cancelada ou substituída
Os itens 2.5.1 e 2.5.2 da NT 008 exigem marca d'água diagonal — "CANCELADA" ou "SUBSTITUÍDA" — no DANFSe da nota que saiu de vigência. Passe-a como segundo argumento:
A marca não sai do XML, e por isso não é inferida: infNFSe/cStat só descreve como
a nota foi gerada (Gerada, Decisão Judicial, Avulsa, MEI), enquanto cancelamento e
substituição chegam depois, como evento separado. Quem consultou os eventos é quem
sabe — omitir o argumento imprime o DANFSe sem marca, como nota vigente.
Debug: obter o HTML intermediário
Diferente de toPdf(), que devolve DanfseResponse com sucesso: false em caso de
falha, toHtml() retorna string e portanto propaga a exceção: XmlParseException
quando o XML está malformado ou não traz algum grupo obrigatório da NFS-e.
Conformidade com a NT 008 (DANFSe v2.0)
O layout segue a Nota Técnica nº 008, que define o DANFSe v2.0. A seção
2.4.5 dela tabula 94 campos, cada um com o caminho no XML de onde sai — e os 94 são
lidos. A tabela está versionada em tests/fixtures/nt008/campos-2.4.5.json, extraída
por tools/extract-nt008.py, e um teste confere a cada execução que cada caminho dela
ainda existe no XSD e que o builder continua lendo todos.
Blocos do documento, na ordem do Anexo I:
| Bloco | Origem no XML |
|---|---|
| Dados da NFS-e | infNFSe/ + infDPS/ |
| Prestador / Fornecedor | infDPS/prest/, com infNFSe/emit/ de reserva |
| Tomador / Adquirente | infDPS/toma/ |
| Destinatário da Operação | infDPS/IBSCBS/dest/ |
| Intermediário da Operação | infDPS/interm/ |
| Serviço Prestado | infDPS/serv/ |
| Tributação Municipal (ISSQN) | infDPS/valores/trib/tribMun/ + infNFSe/valores/ |
| Tributação Federal | infDPS/valores/trib/tribFed/ |
| Tributação IBS / CBS | infDPS/IBSCBS/ + infNFSe/IBSCBS/ |
| Valor Total da NFS-e | infNFSe/valores/ + infNFSe/IBSCBS/totCIBS/ |
| Informações Complementares | dez campos espalhados pelo leiaute (ver abaixo) |
Comportamentos que valem conhecer:
- Prestador sai de
prest, não deemit. É o que a NT determina. ComoxNome,end,fone,emaileIMsão opcionais emTCInfoPrestadore obrigatórios emTCEmitente, cada campo cai ememitquando a DPS o omite — comum, já que o cadastro completo costuma vir do fisco. - Destinatário tem três estados. Bloco completo; "O DESTINATÁRIO É O PRÓPRIO
TOMADOR/ADQUIRENTE DA OPERAÇÃO" quando
indDest = 0; ou "NÃO IDENTIFICADO" quando não há dados. NFS-e anterior à reforma não trazIBSCBSe cai no terceiro. - Duas linhas do bloco ISSQN somem quando vazias (imunidade/suspensão e benefício/deduções), como a nota 5 do item 2.4.5 permite. Um único campo preenchido traz a linha inteira de volta.
-
"Informações Complementares" não é o
xInfComp. O item 2.4.5 manda unir dez campos, cada um com seu rótulo, na ordem da tabela e separados por|:Rótulo Tag Inf. Cont.:serv/infoCompl/xInfCompNFS-e Subst.:subst/chSubstda(nota 7)Doc. Ref.:serv/infoCompl/docRefCod. Obra:serv/obra/cObra(nota 8)Insc. Imob.:IBSCBS/imovel/inscImobFisc(nota 8)Cod. Evt.:serv/atvEvento/idAtvEvt(nota 9)Doc. Tec.:serv/infoCompl/idDocTecNúm. Ped.:serv/infoCompl/xPedItem Ped.:serv/infoCompl/gItemPed/xItemPed(até 99, em lista)Inf. A. T. Mun.:infNFSe/xOutInfCampo ausente some junto com o rótulo —
Cod. Obra: -numa nota que não é de obra gastaria a linha e sugeriria um dado que não existe. - "Total das Retenções (ISSQN / Federais)" é um campo só, como o item 2.1.11 o
define:
vTotalRet, que o fisco já soma. Quando a NFS-e o omite (éminOccurs=0), o SDK refaz a conta que o XSD documenta — Σ(vRetCP + vRetIRRF + vRetCSLL + ISSQN retido). O ISSQN retido aparece no bloco municipal e o PIS/COFINS de apuração própria, no federal; nenhum dos dois é campo do bloco de totais. - Totais aproximados de tributos não têm bloco próprio. A nota 10 os põe dentro de
"Informações Complementares", numa linha fixa e obrigatória. Ela vive em
DanfseTotaisTributos::linhaNt008()e é impressa fora da área que trunca, porque a nota manda que o corte do texto livre seja "sem prejuízo" dela. Os valores saem depTotTrib(percentual) ou, na falta dele, devTotTrib(monetário) — a nota admite os dois. - Descrição do código de tributação é um campo só: municipal quando existe, nacional como alternativa — nunca as duas.
- O canto direito do cabeçalho traz três campos, como manda o item 2.4.3: município
do emitente (
xLocEmi+ UF, 8pt), ambiente gerador e tipo de ambiente (6pt). A linha do município some quando o item do código de tributação nacional é 99 — a própria NT manda não exibi-la ali. - A fonte do conteúdo é a única divergência conhecida. O item 2.4 pede Arial nos rótulos e Microsoft Sans Serif nos conteúdos. A segunda é da Microsoft e não pode ser redistribuída, então o SDK a declara e o Dompdf a usa se você a registrar no seu font dir; sem isso, cai no Helvetica. O DejaVu Sans que vem com o Dompdf seria o fallback óbvio, mas é largo o bastante para levar o pior caso da norma à segunda página — trocaria esta divergência pela do item 2.2, que é pior.
- Marca d'água de cancelamento/substituição vem de fora. O XML não a carrega; ver NFS-e cancelada ou substituída.
- Códigos viram descrições.
cStat,finNFSe,tpEmit,ambGer,tpImunidade,tpSusp,tpBMetpRetPisCofinssão impressos pelo texto do leiaute. Código sem correspondência sai como-: rótulo inventado em documento fiscal é pior que campo vazio.
Objetos do DANFSE
toPdf() e toHtml() montam um NfseData a partir do XML. Ele é público — útil para
quem quer os dados já normalizados (códigos traduzidos, valores formatados) sem gerar
o PDF:
Propriedade de NfseData |
Tipo | Descrição |
|---|---|---|
chaveAcesso, numeroNfse, competencia |
string |
Identificação da NFS-e |
emissaoNfse, numeroDps, serieDps, emissaoDps |
string |
Datas e identificação da DPS |
ambiente |
NfseAmbiente |
Produção ou homologação |
situacao |
string |
Descrição de cStat |
finalidade |
string |
Descrição de finNFSe |
emitidaPor |
string |
Descrição de tpEmit |
ambienteGerador |
string |
Descrição de ambGer |
municipioEmitente |
string |
xLocEmi / UF; vazio quando a NT manda não exibir |
emitente |
DanfseParticipante |
Prestador |
tomador, intermediario, destinatario |
?DanfseParticipante |
null quando ausentes; o bloco vira a frase de "não identificado" da NT |
destinatarioEhTomador |
bool |
indDest = 0 |
servico |
DanfseServico |
Códigos e descrições do serviço |
tribMun, tribFed, tribIbsCbs |
DTOs de tributação | ISSQN, federal e IBS/CBS |
totais, totaisTributos |
DanfseTotais, DanfseTotaisTributos |
Valores e percentuais |
informacoesComplementares |
string |
União dos dez campos, com reticências acima de 1997 caracteres |
marcaDagua |
?MarcaDagua |
"CANCELADA"/"SUBSTITUÍDA"; null na nota vigente |
Em DanfseParticipante, municipio, codigoIbge e cep cobrem os dois ramos de
endereço do leiaute: end/endNac (município da tabela do IBGE e CEP) e end/endExt
(cidade, província e código postal do exterior, este último sem máscara, por ser
alfanumérico). No exterior não há código do IBGE e codigoIbge sai -;
codigoIbgeCep() monta o campo único "CÓDIGO IBGE / CEP" do item 2.4.5, com um lado só
quando o participante está fora do país.
Nome e endereço saem com reticências acima de 77 caracteres, como as descrições de opção do Simples Nacional (37), do regime de apuração pelo SN (77) e do benefício municipal (37) — os limites da tabela do item 2.4.5.
Enums com label(), que devolvem a descrição do leiaute — todos conferidos contra a
<xs:documentation> do XSD por teste:
SituacaoNfse, AmbienteGerador, TipoBeneficioMunicipal, NfseAmbiente,
FinNFSe, TpEmit, TpImunidade, TpSusp, TpRetPisCofins, TpRetISSQN,
TribISSQN, RegEspTrib, OpSimpNac, RegApTribSN, CNaoNIF.
Página única
O item 2.2 da NT exige que o DANFSe caiba em uma página, e ele cabe com os limites da própria NT — 1297 caracteres de descrição do serviço e 1997 de informações complementares, ambos com reticências acima disso. Não há corte extra do SDK.
O que sustenta isso são as medidas da norma, não folga improvisada: margens de 0,176 cm (item 2.2.2 admite de 0,15 a 0,20), rótulos de 6pt e conteúdo de 7pt (item 2.4). Os dois quadros de texto livre crescem à vontade, como o item 2.3.1 prevê ao mandar "aumentar a altura do quadro 'Descrição do Serviço' e/ou 'Informações Complementares'".
No pior caso da norma — todos os blocos preenchidos e os dois campos livres no teto, em
caixa alta — sobram cerca de 6pt na página. É pouco, e por isso
tests/Unit/Danfse/DanfseSinglePageTest.php renderiza o PDF e conta as páginas a cada
execução, incluindo a verificação de que a linha fixa de totais aproximados continua
impressa.
Geração automática do DANFSE
Desligada por padrão. Ligada, anexa o PDF ao NfseResponse em emitir(),
substituir() e consultar()->nfse() — cerca de 300 ms e
15 KB por nota, gastos mesmo que ninguém abra o documento. Sem ela, gere sob demanda
com $client->danfse()->toPdf($resp->xml).
A flag é lida com cast para bool. Se a config vier de outra fonte que não env() — banco,
YAML, painel —, converta antes: 'false', 'off' e 'no' são strings verdadeiras em PHP
e ligariam o auto-render.
Ligar pontualmente (mesmo com NFSE_AUTO_DANFSE=false):
Desligar pontualmente (mesmo com NFSE_AUTO_DANFSE=true):
A facade Nfsen::for() aceita o mesmo parâmetro: Nfsen::for($pfx, $senha, $ibge, danfse: true).
Quando o PDF falha ($resp->sucesso === true mas $resp->pdf === null): a NFS-e
foi emitida com sucesso. Regenere sob demanda com $client->danfse()->toPdf($resp->xml)
e inspecione $resp->pdfErrors.
Atribuição
A renderização do DANFSE foi portada da biblioteca andrevabo/danfse-nacional (MIT) e adaptada à arquitetura deste SDK. A tabela de municípios IBGE vem de kelvins/municipios-brasileiros (MIT).
Exemplos
Exemplos completos de cada operação estão disponíveis no diretório examples/.
Testes
Para executar todas as verificações de qualidade:
Contribuindo
Veja CONTRIBUTING.md para detalhes.
Créditos
Este pacote teve como base o trabalho do projeto original nfse-nacional de Fernando Friedrich, que por sua vez foi construído sobre o NFePHP de Roberto L. Machado.
Agradecimento a todos os contribuidores que ajudaram a evoluir este projeto.
Licença
MIT. Veja LICENSE.
All versions of nfsen-php-sdk with dependencies
nfephp-org/sped-common Version ^5.1
dompdf/dompdf Version ^3.0
bacon/bacon-qr-code Version ^3.0
guzzlehttp/guzzle Version ^7.5
illuminate/http Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0
illuminate/contracts Version ^11.0|^12.0|^13.0
ext-curl Version *
ext-dom Version *
ext-zlib Version *
ext-openssl Version *
ext-mbstring Version *
ext-libxml Version *
ext-simplexml Version *