jonathanpmartins/nfse-nacional

Pacote Laravel para emissão de NFSe Nacional

Maintainers

Package info

github.com/jonathanpmartins/nfse-nacional

pkg:composer/jonathanpmartins/nfse-nacional

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

dev-main 2026-03-07 04:53 UTC

This package is auto-updated.

Last update: 2026-03-07 04:53:36 UTC


README

CI Latest Version on Packagist PHP Version License

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 ou standalone (sem framework).

Funcionalidades

  • Emissão de NFSe (emitir) e emissão por decisão judicial (emitirDecisaoJudicial)
  • Cancelamento de NFSe (cancelar)
  • Substituição de NFSe (substituir)
  • Consulta por chave de acesso, DPS, DANFSE (URL do PDF), eventos e verificação de DPS
  • Assinatura digital XML com certificado A1 (PFX/P12)
  • Validação XSD dos documentos
  • Eventos Laravel opcionais (NfseEmitted, NfseCancelled, NfseRejected, etc.)
  • mTLS sem escrita nomeada em disco
  • 100% de cobertura de testes e tipos

Requisitos

  • PHP 8.2+
  • Extensões: curl, dom, zlib, openssl, mbstring, libxml
  • Laravel 11 ou 12 (opcional — funciona standalone)

Instalação

composer require jonathanpmartins/nfse-nacional

Configuração

Laravel

Publique o arquivo de configuração:

php artisan vendor:publish --tag=nfse-nacional-config

Adicione as variáveis de ambiente no .env:

NFSE_AMBIENTE=2                   # 1 = Producao, 2 = Homologacao
NFSE_PREFEITURA=3550308           # Codigo IBGE do municipio (7 digitos)
NFSE_CERT_PATH=/caminho/cert.pfx  # Caminho do certificado PFX/P12
NFSE_CERT_SENHA=senha             # Senha do certificado
NFSE_TIMEOUT=30
NFSE_CONNECT_TIMEOUT=10
NFSE_SIGNING_ALGORITHM=sha1
NFSE_SSL_VERIFY=true

Standalone (sem Laravel)

use Pulsar\NfseNacional\Enums\NfseAmbiente;
use Pulsar\NfseNacional\NfseClient;

$client = NfseClient::forStandalone(
    pfxContent: file_get_contents('/caminho/certificado.pfx'),
    senha: 'senha_certificado',
    prefeitura: '3550308',
    ambiente: NfseAmbiente::HOMOLOGACAO,
);

Uso

Emitir NFSe

$response = $client->emitir([
    'infDPS' => [
        'tpAmb'    => '2',                          // 1 = Producao, 2 = Homologacao
        'dhEmi'    => date('Y-m-d\TH:i:sP'),       // Data/hora emissao
        'verAplic' => 'MeuSistema_v1.0',
        'serie'    => '1',
        'nDPS'     => '1',
        'dCompet'  => date('Y-m-d'),                // Data de competencia
        'tpEmit'   => '1',                          // 1 = Prestador
        'cLocEmi'  => '3550308',                    // Codigo IBGE 7 digitos
    ],
    'prest' => [
        'CNPJ'    => '00000000000000',
        'fone'    => '11999999999',
        'regTrib' => [
            'opSimpNac'   => '2',                   // 1 = Nao Optante, 2 = MEI, 3 = ME/EPP
            'regEspTrib'  => '0',                   // 0 = Nenhum
        ],
    ],
    'toma' => [
        'xNome' => 'Tomador Exemplo Ltda',
        'CPF'   => '00000000000',
        'end'   => [
            'xLgr'   => 'Rua Exemplo',
            'nro'    => '100',
            'xBairro' => 'Centro',
            'endNac' => [
                'cMun' => '3550308',
                'CEP'  => '01001000',
            ],
        ],
    ],
    'serv' => [
        'cLocPrestacao' => '3550308',
        'cServ' => [
            'cTribNac'    => '010101',
            'xDescServ'   => 'Desenvolvimento de software sob encomenda',
            'cNBS'        => '116030000',
            'cIntContrib' => '1234',
        ],
    ],
    'valores' => [
        'vServPrest' => ['vServ' => '1000.00'],
        'trib' => [
            'tribMun' => [
                'tribISSQN'  => '1',               // 1 = Operacao tributavel
                'tpRetISSQN' => '1',               // 1 = Nao retido
            ],
            'indTotTrib' => '0',
        ],
    ],
]);

if ($response->sucesso) {
    echo "Chave: {$response->chave}\n";
    echo "XML: {$response->xml}\n";
} else {
    foreach ($response->erros as $erro) {
        echo "[{$erro->codigo}] {$erro->mensagem} - {$erro->descricao}";
    }
}

Cancelar NFSe

use Pulsar\NfseNacional\Enums\CodigoJustificativaCancelamento;

$response = $client->cancelar(
    chave: '00000000000000000000000000000000000000000000000000',
    codigoMotivo: CodigoJustificativaCancelamento::ErroEmissao,
    descricao: 'Erro na emissao da nota fiscal',
);

Codigos de cancelamento: ErroEmissao, ServicoNaoPrestado, Outros.

Substituir NFSe

A substituição na API Nacional envolve duas etapas: emitir a nota substituta e registrar o evento de cancelamento por substituição (e105102). A lib orquestra as duas etapas internamente — basta informar a chave da nota original, a DPS da nota substituta e o motivo.

substituir(chave, dps, motivo)
│
├─ Etapa 1: Emite a NFS-e substituta (injeta campo `subst` na DPS automaticamente)
│  ├─ Falhou? → retorna com emissao.sucesso = false, evento = null
│  └─ Sucesso → continua
│
└─ Etapa 2: Registra evento e105102 (Cancelamento por Substituição)
   ├─ Falhou? → retorna com emissao.sucesso = true, evento.sucesso = false
   └─ Sucesso → retorna com sucesso = true

Atenção: é possível que a emissão da nota substituta tenha sucesso mas o registro do evento falhe. Nesse caso, a nota substituta já foi emitida na base da Receita Federal. Verifique sempre $response->emissao e $response->evento separadamente para tratar esse cenário.

use Pulsar\NfseNacional\Enums\CodigoJustificativaSubstituicao;

$response = $client->substituir(
    chave: '00000000000000000000000000000000000000000000000000',
    dps: $dpsSubstituta, // DPS da nota substituta (mesma estrutura do emitir)
    codigoMotivo: CodigoJustificativaSubstituicao::Outros,
    descricao: 'Substituicao por correcao de dados',
);

// SubstituicaoResponse:
// - $response->sucesso   (bool)          true somente se AMBAS as etapas deram certo
// - $response->emissao   (NfseResponse)  resultado da emissão (sempre presente)
// - $response->evento    (?NfseResponse) resultado do registro do evento (null se emissão falhou)

if ($response->sucesso) {
    // Tudo certo: nota substituta emitida e evento registrado
    echo "Chave substituta: {$response->emissao->chave}";

} elseif (! $response->emissao->sucesso) {
    // Emissão falhou — nenhuma nota foi gerada
    foreach ($response->emissao->erros as $erro) {
        echo "[{$erro->codigo}] {$erro->descricao}\n";
    }

} else {
    // Nota substituta FOI emitida, mas o registro do evento falhou.
    // A nota substituta já existe na base da Receita.
    echo "Nota emitida: {$response->emissao->chave}\n";
    echo "Evento não registrado — tente novamente ou registre manualmente.\n";
    foreach ($response->evento->erros as $erro) {
        echo "[{$erro->codigo}] {$erro->descricao}\n";
    }
}

Codigos de substituição: DesenquadramentoSimplesNacional, EnquadramentoSimplesNacional, InclusaoRetroativaImunidadeIsencao, ExclusaoRetroativaImunidadeIsencao, RejeicaoTomadorIntermediario, Outros.

Confirmar substituição (apenas etapa 2)

Se você já emitiu a nota substituta por conta própria, ou se a etapa 2 do substituir falhou e precisa ser refeita, use confirmarSubstituicao para registrar apenas o evento de cancelamento por substituição:

$response = $client->confirmarSubstituicao(
    chaveSubstituida: '00000000000000000000000000000000000000000000000000',
    chaveSubstituta: '11111111111111111111111111111111111111111111111111',
    codigoMotivo: CodigoJustificativaSubstituicao::Outros,
    descricao: 'Substituicao por correcao de dados',
); // NfseResponse

Consultas

use Pulsar\NfseNacional\Enums\TipoEvento;

// Consultar NFSe por chave de acesso
$response = $client->consultar()->nfse($chave);

// Consultar DPS por ID
$response = $client->consultar()->dps($idDps);

// Obter PDF do DANFSE
$response = $client->consultar()->danfse($chave);
// $response->pdf contém o conteúdo binário do PDF
file_put_contents('danfse.pdf', $response->pdf);

// Consultar eventos
$response = $client->consultar()->eventos(
    chave: $chave,
    tipoEvento: TipoEvento::CancelamentoPorIniciativaPrestador,
    nSequencial: 1,
);

// Verificar se DPS foi processada
$processada = $client->consultar()->verificarDps($idDps); // true ou false

Laravel Facade

use Pulsar\NfseNacional\Facades\NfseNacional;

// Emitir
$response = NfseNacional::emitir($dps);

// Cancelar
$response = NfseNacional::cancelar($chave, $motivo, $descricao);

// Consultar
$response = NfseNacional::consultar()->nfse($chave);
$danfse   = NfseNacional::consultar()->danfse($chave);

// Usar certificado diferente por requisicao
$client = NfseNacional::for($pfxContent, $senha, '3550308');
$response = $client->emitir($dps);

// Sobrescrever ambiente (ignorar config)
use Pulsar\NfseNacional\Enums\NfseAmbiente;

$client = NfseNacional::for($pfxContent, $senha, '3550308', NfseAmbiente::PRODUCAO);
$response = $client->emitir($dps);

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
NfseSubstituted chave, chaveSubstituta Evento de substituição registrado com sucesso
NfseQueried operacao Consulta realizada
NfseRequested operacao, metadata Operação iniciada
NfseRejected operacao, codigoErro Operação rejeitada pela API
NfseFailed operacao, message Falha na operação

Substituição: como substituir executa emissão + registro de evento internamente, a sequência de eventos disparados é: NfseRequested('emitir')NfseEmittedNfseRequested('substituir')NfseSubstituted

Exemplos

Exemplos completos de cada operação estão disponíveis no diretório examples/.

Testes

composer test

Para executar todas as verificações de qualidade:

composer quality

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.