Looking to hire Laravel developers? Try LaraJobs

laravel-notas-fiscais maintained by danielbbarcelos

Description
Emissão de notas fiscais de serviço (NFS-e) sob um contrato único, com drivers por provedor (IPM Atende.Net, ...). Laravel 11+.
Last update
2026/07/01 14:39 (dev-main)
License
Links
Downloads
0

Comments
comments powered by Disqus

laravel-notas-fiscais

Latest Version on Packagist License: MIT PHP Version Laravel

Repositório: github.com/danielbbarcelos/laravel-notas-fiscais

Emissão de notas fiscais de serviço (NFS-e) sob um contrato único, com drivers por provedor. Laravel 11+.

Hoje implementa o provedor IPM Atende.Net (NFS-e municipal) em dois padrões. A arquitetura de contracts é genérica para acomodar novos provedores — e, futuramente, outros tipos de documento (NF-e, NFC-e).

Qual driver usar (IPM)

O Atende.Net expõe a NFS-e em dois padrões diferentes, por município:

Driver Padrão Serviço Quando usar
ipm REST proprietário (NTE 35/2021) WNERestServiceNFSe Municípios com o REST <nfse>/multipart
ipm-abrasf ABRASF 2.04 (SOAP) WNENotaFiscalEletronicaNfe Municípios ABRASF — ex.: Pouso Alegre/MG

Ambos autenticam por login/senha (Basic) — sem certificado. Para descobrir qual o município usa, teste o endpoint: se o REST responder 503 "Serviço não disponível" e o SOAP responder, use ipm-abrasf. Note que o ABRASF identifica o município pelo código IBGE (7 dígitos), enquanto o REST usa o código TOM.

Notas práticas do ABRASF (validado em produção — Pouso Alegre/MG)

Emissão e cancelamento reais já validados ponta a ponta. Pontos que costumam barrar:

  • Autenticação: a senha é a específica do WebService (pode diferir da senha do portal). 401 "Acesso Negado" = credencial/login rejeitado.
  • Competência (NotaServico::competencia): use a data atual; competência retroativa é recusada (L1029) salvo autorização do município.
  • ItemServico::codigoItemListaServico: deve estar no formato pontuado (99.99, 99.99.99 ou 99.99.99.999) e vinculado ao cadastro econômico do prestador. Ex.: o código 10101 do portal vira 01.01.01 (zero à esquerda). Em formato errado → L1099; código não vinculado → L1003.
  • CNAE (ItemServico::codigoCnae) e item devem ser coerentes entre si.
  • Modo teste: o <EnvioTeste> exige a operação de lote síncrono, que em alguns municípios (Pouso Alegre) tem bug no servidor. Para validar, prefira **emitir real
    • cancelar** (a operação GerarNfse, usada na emissão padrão, é a estável).
  • Link do PDF: o GerarNfseResposta desses municípios não retorna o link. Configure link_template (config notas-fiscais.drivers.ipm-abrasf.link_template, env IPM_ABRASF_LINK_TEMPLATE) com a URL de consulta/autenticidade do Atende.Net e o placeholder {codigo} (e opcionalmente {numero}); o driver preenche NotaEmitida::link automaticamente. Para achar a URL e o {ID} do serviço, abra uma NFS-e na consulta de autenticidade do portal e copie a URL, trocando o código por {codigo}. Ex.: https://{cidade}.atende.net/?pg=autoatendimento#!/tipo/servico/valor/213/padrao/1/load/1/identificador/{codigo} (valor/213 é o serviço de autenticidade de NFS-e, global no Atende.Net). Confirmado para Pouso Alegre/MG (abre a nota com botões XML IPM / XML Abrasf / Imprimir): https://pousoalegre.atende.net/autoatendimento/servicos/consulta-de-autenticidade-de-nota-fiscal-eletronica-nfse/detalhar/1/identificador/{codigo}

Instalação

composer require danielbbarcelos/laravel-notas-fiscais
php artisan vendor:publish --tag=notas-fiscais-config

Integrando numa API? Veja o Guia de integração Laravel — passo a passo multi-tenant/SaaS com endpoints REST, service, persistência e PDF.

Configuração

config/notas-fiscais.php (ou via .env):

NFSE_DRIVER=ipm
IPM_BASE_URL=https://riodosul.atende.net/atende.php?pg=rest&service=WNERestServiceNFSe&cidade=padrao
IPM_CPF_CNPJ=12345678000199   # login do Web Service (prestador)
IPM_SENHA=sua-senha
IPM_CIDADE_TOM=8055           # código TOM do município do prestador

A URL do Web Service varia por município. Solicite acesso no Portal do Cidadão da prefeitura ("Emissão de NFS-e por WebService").

Uso

use DanielBBarcelos\NotasFiscais\Facades\NotaFiscal;
use DanielBBarcelos\NotasFiscais\Data\Nfse\{NotaServico, ItemServico, Tomador};
use DanielBBarcelos\NotasFiscais\Data\Shared\{Valor, Endereco};
use DanielBBarcelos\NotasFiscais\Enums\{TipoTomador, SituacaoTributaria};

$nota = new NotaServico(
    serie: 1,
    dataFatoGerador: '15/01/2026',
    valorTotal: Valor::reais('1000.00'),
    tomador: new Tomador(
        tipo: TipoTomador::Juridica,
        identificacao: '12.345.678/0001-95',
        nomeRazaoSocial: 'Empresa Tomadora LTDA',
        endereco: new Endereco(logradouro: 'Rua das Flores', numero: '123', bairro: 'Centro', codigoMunicipio: '8055', cep: '89160-000'),
        email: 'tomador@exemplo.com.br',
    ),
    itens: [
        new ItemServico(
            codigoItemListaServico: '010700',
            descritivo: 'Desenvolvimento de software sob encomenda',
            aliquota: 3.0,
            situacaoTributaria: SituacaoTributaria::TributadaIntegralmente,
            valorTributavel: Valor::reais('1000.00'),
            codigoLocalPrestacao: '8055',
        ),
    ],
);

// Driver padrão (config) ou explícito:
$emitida = NotaFiscal::nfse()->emitir($nota);
$emitida = NotaFiscal::driver('ipm')->nfse()->emitir($nota);

$emitida->numero;             // 1293
$emitida->codigoVerificacao;  // 8357...913
$emitida->link;               // URL do PDF
$emitida->emitida();          // true

Cancelamento e consulta:

use DanielBBarcelos\NotasFiscais\Data\Nfse\Cancelamento;

NotaFiscal::nfse()->cancelar(new Cancelamento(numero: 1293, serie: 1, motivo: 'Erro de digitação'));

NotaFiscal::nfse()->consultar(numero: 1293, serie: 1, cadastro: '8055');
NotaFiscal::nfse()->consultarPorAutenticidade('8357...913');

Erros do provedor lançam NotaFiscalApiException (com ->codigo e ->corpo); documento não suportado lança OperacaoNaoSuportadaException. Ambas estendem NotaFiscalException.

Comprovante da NFS-e em PDF

O PDF oficial do município (link /ged/r/{hash} do Atende.Net) é gerado por um endpoint protegido por captcha (Cloudflare Turnstile) na via pública — então não é automatizável. Para anexar em e-mail / download, o pacote gera um comprovante próprio em PDF a partir dos dados canônicos, sem captcha. O PDF deixa explícito que não é o documento fiscal oficial e aponta, no topo, o link de autenticidade onde a NFS-e oficial pode ser consultada e impressa (e que reflete o status atual):

use DanielBBarcelos\NotasFiscais\Pdf\ComprovanteNfse;

$emitida = NotaFiscal::driver('ipm-abrasf')->nfse()->emitir($nota);

$bytes = ComprovanteNfse::gerar($nota, $emitida);              // bytes do PDF (anexar em e-mail)
ComprovanteNfse::salvar('/tmp/nfse-57.pdf', $nota, $emitida);  // salva em arquivo

Se a emissão usou o prestador da config (NotaServico sem prestador), passe-o no 3º argumento. O 4º argumento (Emitente) coloca logo e nome da empresa no cabeçalho — ambos opcionais (sem eles, o comprovante usa placeholders):

use DanielBBarcelos\NotasFiscais\Pdf\Emitente;

ComprovanteNfse::salvar('/tmp/nfse-57.pdf', $nota, $emitida, $prestador, new Emitente(
    nome: 'Minha Empresa LTDA',
    logo: '/caminho/absoluto/logo.png',   // png, jpg, gif ou svg
));

Requer dompdf (dependência opcional, PHP puro, sem binário):

composer require dompdf/dompdf

Multi-tenant / SaaS (credenciais em runtime)

O .env é só o default opcional (single-tenant). Não guarde credenciais de clientes no .env/config num SaaS: a aplicação armazena com segurança (ex.: coluna criptografada por tenant) e injeta em runtime.

// Config completa do tenant, sem passar pelo .env (recomendado p/ SaaS):
NotaFiscal::build([
    'driver'   => 'ipm',
    'base_url' => $tenant->ipmUrl,
    'cpf_cnpj' => $tenant->cnpj,
    'senha'    => decrypt($tenant->ipmSenha),
    'cidade'   => $tenant->codigoTom,
])->nfse()->emitir($nota);

// Ou sobrepondo apenas alguns campos sobre a base nomeada:
NotaFiscal::driver('ipm', [
    'cpf_cnpj' => $tenant->cnpj,
    'senha'    => decrypt($tenant->ipmSenha),
])->nfse()->emitir($nota);

Instâncias resolvidas em runtime não são cacheadas pelo nome (sem vazamento entre tenants), e a sessão (PHPSESSID) é isolada por credencial. Para uso fixo single-tenant, NotaFiscal::nfse() segue lendo a config nomeada normalmente.

Playground (validação visual)

Para validar campos e retornos numa tela, ligue a demo (mantenha desligada em produção):

NFSE_DEMO=true

Isso registra a rota GET/POST /notas-fiscais/demo com um formulário que monta o NotaServico, mostra o XML gerado e o NotaEmitida parseado (ou o erro). Um toggle alterna entre:

  • Faked — devolve os XMLs de exemplo dos docs (sucesso/erro/cancelada); valida o fluxo inteiro sem credenciais nem prefeitura;
  • IPM real — usa as credenciais do .env (com <nfse_teste> por padrão).

Dentro do próprio repositório do pacote, sirva via Testbench:

NFSE_DEMO=true vendor/bin/testbench serve
# acesse http://127.0.0.1:8000/notas-fiscais/demo

A demo vive em routes/demo.php + resources/views/demo.blade.php e só é carregada quando notas-fiscais.demo é true.

Novos provedores

Implemente Contracts\Provedor + Contracts\NfseGateway e registre no boot:

NotaFiscal::extend('meu-provedor', fn (array $config, string $nome) => new MeuProvedor($config, $nome));

Testes

composer test

Contribuindo

Issues e pull requests são bem-vindos em github.com/danielbbarcelos/laravel-notas-fiscais.

Licença

Distribuído sob a licença MIT. Veja LICENSE.