# SPED-NFe O **sped-nfe** (`nfephp-org/sped-nfe`) é uma biblioteca PHP para geração, assinatura e comunicação de Notas Fiscais Eletrônicas (NF-e, modelo 55) e Notas Fiscais de Consumidor Eletrônicas (NFC-e, modelo 65) com as SEFAZ autorizadoras de todos os estados brasileiros. A biblioteca cobre todo o ciclo de vida do documento fiscal eletrônico: desde a montagem do XML, passando pela assinatura digital com certificado A1/A3, até o envio e recebimento de respostas dos webservices SOAP da SEFAZ, incluindo todos os eventos (cancelamento, carta de correção, manifestação do destinatário, etc.). A biblioteca segue os padrões PSR-1, PSR-2 e PSR-4 e é compatível com PHP >= 7.4. Seu uso exige o Composer para instalação, e depende do pacote `nfephp-org/sped-common` para operações de certificado digital, assinatura XML e comunicação SOAP. As principais classes de trabalho são `Make` (montagem do XML da NF-e), `Tools` (comunicação com a SEFAZ) e `Complements` (protocolação e pós-processamento dos documentos). --- ## Instalação ```bash composer require nfephp-org/sped-nfe ``` --- ## Configuração da classe Tools A classe `Tools` recebe um JSON de configuração e um objeto `Certificate` carregado a partir de um arquivo PFX (certificado digital A1). ```php "2024-01-01 00:00:00", "tpAmb" => 2, // 1=Produção, 2=Homologação "razaosocial" => "Empresa Exemplo Ltda", "siglaUF" => "SP", // UF do emitente "cnpj" => "00716345000119", "schemes" => "PL_009_V4", // versão do schema SEFAZ "versao" => "4.00", "tokenIBPT" => "", "CSC" => "GPB0JBWLUR6HWFTVEAS6RJ69GPCROFPBBB8G", // obrigatório para NFCe "CSCid" => "000001", "aProxyConf" => [ "proxyIp" => "", "proxyPort" => "", "proxyUser" => "", "proxyPass" => "" ] ]; $configJson = json_encode($config); $pfxContent = file_get_contents('/caminho/para/certificado.pfx'); $certificate = Certificate::readPfx($pfxContent, 'senha_do_certificado'); // Instancia para NF-e (modelo 55) $tools = new Tools($configJson, $certificate); $tools->model('55'); // Para NFC-e use model('65') // $tools->model('65'); ``` --- ## Make — Montagem do XML da NF-e A classe `Make` constrói o XML da NF-e/NFC-e a partir de objetos `stdClass`. Cada tag obrigatória do leiaute SEFAZ possui um método dedicado. Ao final, `getXML()` retorna o XML sem assinatura. ```php Id = ''; $std->versao = '4.00'; $make->taginfNFe($std); // --- ide (identificação da NF-e) --- $std = new stdClass(); $std->cUF = 35; // código IBGE da UF (SP=35) $std->cNF = '12345678'; // código numérico aleatório $std->natOp = 'VENDA DE MERCADORIA'; $std->mod = 55; // 55=NF-e, 65=NFC-e $std->serie = 1; $std->nNF = 1001; $std->dhEmi = (new \DateTime())->format('Y-m-d\TH:i:sP'); $std->dhSaiEnt = null; $std->tpNF = 1; // 0=Entrada, 1=Saída $std->idDest = 1; // 1=Operação interna $std->cMunFG = 3550308; // cód. IBGE município (São Paulo) $std->tpImp = 1; $std->tpEmis = 1; $std->cDV = 0; $std->tpAmb = 2; // 2=Homologação $std->finNFe = 1; $std->indFinal = 0; $std->indPres = 0; $std->procEmi = 0; $std->verProc = '1.0.0'; $std->dhCont = null; $std->xJust = null; $make->tagIde($std); // --- emit (emitente) --- $std = new stdClass(); $std->CNPJ = '00716345000119'; $std->xNome = 'Empresa Exemplo Ltda'; $std->xFant = 'Exemplo'; $std->IE = '111111111111'; $std->CRT = 1; // 1=Simples Nacional $make->tagemit($std); // --- enderEmit --- $std = new stdClass(); $std->xLgr = 'Rua das Flores'; $std->nro = '100'; $std->xCpl = null; $std->xBairro = 'Centro'; $std->cMun = 3550308; $std->xMun = 'São Paulo'; $std->UF = 'SP'; $std->CEP = '01001000'; $std->cPais = 1058; $std->xPais = 'BRASIL'; $std->fone = '1100000000'; $make->tagenderEmit($std); // --- dest (destinatário) --- $std = new stdClass(); $std->CNPJ = '12345678000195'; $std->xNome = 'Destinatário Exemplo Ltda'; $std->indIEDest = 1; $std->IE = '222222222222'; $std->email = 'dest@exemplo.com.br'; $make->tagdest($std); // --- enderDest --- $std = new stdClass(); $std->xLgr = 'Av. Brasil'; $std->nro = '200'; $std->xCpl = 'Sala 5'; $std->xBairro = 'Jardim'; $std->cMun = 3304557; $std->xMun = 'Rio de Janeiro'; $std->UF = 'RJ'; $std->CEP = '20040020'; $std->cPais = 1058; $std->xPais = 'BRASIL'; $std->fone = '2100000000'; $make->tagenderDest($std); // --- det / prod (item 1) --- $std = new stdClass(); $std->item = 1; $std->cProd = 'PROD001'; $std->cEAN = 'SEM GTIN'; $std->xProd = 'Produto de Exemplo'; $std->NCM = '84713019'; $std->CFOP = '6102'; $std->uCom = 'UN'; $std->qCom = 1.0000; $std->vUnCom = 100.00; $std->vProd = 100.00; $std->cEANTrib = 'SEM GTIN'; $std->uTrib = 'UN'; $std->qTrib = 1.0000; $std->vUnTrib = 100.00; $std->vFrete = null; $std->vSeg = null; $std->vDesc = null; $std->vOutro = null; $std->indTot = 1; $make->tagprod($std); // --- ICMS (CSOSN 400 – Simples Nacional) --- $std = new stdClass(); $std->item = 1; $std->orig = 0; $std->CSOSN = 400; $make->tagICMSSN($std); // --- PIS e COFINS (não tributado) --- $std = new stdClass(); $std->item = 1; $std->CST = '07'; $make->tagPISNT($std); $std = new stdClass(); $std->item = 1; $std->CST = '07'; $make->tagCOFINSNT($std); // --- pag (pagamento) --- $std = new stdClass(); $std->vTroco = null; $make->tagpag($std); $std = new stdClass(); $std->item = 1; $std->tPag = '01'; // 01=Dinheiro $std->vPag = 100.00; $std->indPag = 0; $make->tagdetPag($std); // Gera o XML (sem assinatura) $xml = $make->getXML(); // Verifica erros de montagem $erros = $make->getErrors(); if (!empty($erros)) { foreach ($erros as $erro) { echo "Erro: $erro\n"; } } echo $xml; // XML da NF-e pronto para assinar e enviar ``` --- ## Tools::sefazEnviaLote — Autorização de NF-e em lote Envia um lote de NF-e (modelo 55 ou NFC-e modelo 65) para autorização na SEFAZ. O parâmetro `$indSinc=1` ativa o modo síncrono (apenas um documento por vez). Retorna o XML de resposta SOAP da SEFAZ. ```php model('55'); // $xmlAssinado = XML já assinado com Signer::sign(...) $xmlAssinado = file_get_contents('/tmp/nfe_assinada.xml'); try { // Envio assíncrono de lote com múltiplas NF-e $idLote = '000000001'; $response = $tools->sefazEnviaLote([$xmlAssinado], $idLote, 0); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); if ($std->cStat == 103) { // Lote recebido com sucesso; consultar pelo recibo $recibo = $std->infRec->nRec; echo "Lote aceito. Recibo: $recibo\n"; // Aguarda processamento e consulta sleep(5); $retResponse = $tools->sefazConsultaRecibo($recibo); $retStd = (new \NFePHP\NFe\Common\Standardize($retResponse))->toStd(); foreach ($retStd->protNFe as $prot) { if ($prot->infProt->cStat == 100) { // NF-e autorizada — protocola o XML $xmlProtocolado = Complements::toAuthorize($tools->lastRequest, $retResponse); file_put_contents('/tmp/nfe_protocolada.xml', $xmlProtocolado); echo "NF-e autorizada: " . $prot->infProt->chNFe . "\n"; } } } elseif ($std->cStat == 104) { // Lote processado de forma síncrona $xmlProtocolado = Complements::toAuthorize($tools->lastRequest, $response); } else { echo "Rejeição: [{$std->cStat}] {$std->xMotivo}\n"; } } catch (\Exception $e) { echo "Erro: " . $e->getMessage() . "\n"; } ``` --- ## Tools::sefazConsultaRecibo — Consulta de recibo de lote Verifica o resultado do processamento de um lote enviado de forma assíncrona usando o número do recibo retornado pelo `sefazEnviaLote`. ```php sefazConsultaRecibo($recibo); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); // cStat 104 = Lote processado if ($std->cStat == 104) { foreach ((array)$std->protNFe as $prot) { $cStat = $prot->infProt->cStat; $chNFe = $prot->infProt->chNFe; echo "NF-e $chNFe → cStat: $cStat\n"; // 100 = Autorizado o uso da NF-e } } } catch (\InvalidArgumentException $e) { echo "Parâmetro inválido: " . $e->getMessage() . "\n"; } ``` --- ## Tools::sefazConsultaChave — Consulta situação pela chave de acesso Consulta a situação atual de uma NF-e na SEFAZ a partir da chave de acesso de 44 dígitos. Retorna o protocolo de autorização ou rejeição. ```php sefazConsultaChave($chave); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); if (isset($std->protNFe)) { $cStat = $std->protNFe->infProt->cStat; $nProt = $std->protNFe->infProt->nProt; $dhRecbto = $std->protNFe->infProt->dhRecbto; echo "Situação: $cStat | Protocolo: $nProt | Recebido em: $dhRecbto\n"; } else { echo "NF-e não encontrada ou rejeitada: {$std->xMotivo}\n"; } } catch (\InvalidArgumentException $e) { echo "Chave inválida: " . $e->getMessage() . "\n"; } ``` --- ## Tools::sefazStatus — Consulta status do serviço SEFAZ Verifica se o serviço da SEFAZ para a UF configurada está operacional. Pode ser chamado sem parâmetros para usar a UF do `config.json` ou passando uma UF específica. ```php sefazStatus(); // Consulta status de uma UF específica (ignora contingência ativa) // $response = $tools->sefazStatus('RJ'); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); // cStat 107 = Serviço em Operação if ($std->cStat == 107) { echo "Serviço SEFAZ operacional: {$std->xMotivo}\n"; } else { echo "Serviço indisponível [{$std->cStat}]: {$std->xMotivo}\n"; } ``` --- ## Tools::sefazCancela — Cancelamento de NF-e Envia o evento de cancelamento de uma NF-e autorizada. A justificativa deve ter entre 15 e 255 caracteres e o protocolo de autorização é obrigatório. ```php sefazCancela($chave, $xJust, $nProt); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); if ($std->cStat == 128) { $cStatEvt = $std->retEvento->infEvento->cStat; if (in_array($cStatEvt, [101, 135, 155])) { // Cancelamento autorizado — protocola e armazena $xmlProtocolado = Complements::toAuthorize($tools->lastRequest, $response); file_put_contents('/tmp/cancelamento_protocolado.xml', $xmlProtocolado); echo "NF-e cancelada com sucesso. cStat: $cStatEvt\n"; } else { echo "Cancelamento negado [{$cStatEvt}]: {$std->retEvento->infEvento->xMotivo}\n"; } } else { echo "Erro no lote de evento [{$std->cStat}]: {$std->xMotivo}\n"; } } catch (\InvalidArgumentException $e) { echo "Parâmetros inválidos: " . $e->getMessage() . "\n"; } ``` --- ## Tools::sefazCCe — Carta de Correção Eletrônica Envia uma Carta de Correção Eletrônica (CC-e) para corrigir informações em uma NF-e autorizada. Não é permitida a correção de dados que alteram o valor do imposto, dados do emitente/destinatário ou data de emissão. ```php sefazCCe($chave, $xCorrecao, $nSeqEvento); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); if ($std->cStat == 128) { $cStatEvt = $std->retEvento->infEvento->cStat; if ($cStatEvt == 135) { $xmlProtocolado = \NFePHP\NFe\Complements::toAuthorize($tools->lastRequest, $response); echo "CC-e registrada. Evento: $cStatEvt\n"; } else { echo "CC-e negada [{$cStatEvt}]: {$std->retEvento->infEvento->xMotivo}\n"; } } } catch (\InvalidArgumentException $e) { echo "Erro: " . $e->getMessage() . "\n"; } ``` --- ## Tools::sefazManifesta — Manifestação do Destinatário Registra a manifestação do destinatário (ciência, confirmação, desconhecimento ou operação não realizada) para NF-e recebidas. ```php sefazManifesta($chave, Tools::EVT_CIENCIA); // 2. Confirmação da operação $response = $tools->sefazManifesta($chave, Tools::EVT_CONFIRMACAO); // 3. Operação não realizada (requer justificativa) $xJust = 'Mercadoria recusada pois não corresponde ao pedido'; $response = $tools->sefazManifesta($chave, Tools::EVT_NAO_REALIZADA, $xJust); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); if ($std->cStat == 128 && $std->retEvento->infEvento->cStat == 135) { echo "Manifestação registrada com sucesso.\n"; } ``` --- ## Tools::sefazDistDFe — Distribuição de DF-e Obtém documentos fiscais eletrônicos de interesse do contribuinte (NF-e recebidas, eventos) a partir do Ambiente Nacional da SEFAZ, usando NSU (Número Sequencial Único). ```php sefazDistDFe($ultNSU); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); if (isset($std->loteDistDFeInt->docZip)) { $docs = is_array($std->loteDistDFeInt->docZip) ? $std->loteDistDFeInt->docZip : [$std->loteDistDFeInt->docZip]; foreach ($docs as $doc) { $xmlDecoded = gzdecode(base64_decode((string)$doc)); $schema = $doc->{'@attributes'}->schema ?? ''; echo "Schema: $schema\n"; // Armazenar $xmlDecoded conforme necessidade } // Gravar o último NSU para próxima consulta $ultimoNSU = $std->loteDistDFeInt->ultNSU; echo "Último NSU: $ultimoNSU\n"; } // Consulta um NSU específico // $response = $tools->sefazDistDFe(0, 123456); // Consulta por chave de acesso // $response = $tools->sefazDistDFe(0, 0, '35240100716345000119550010000010011000000105'); ``` --- ## Tools::sefazInutiliza — Inutilização de numeração Solicita à SEFAZ a inutilização de uma faixa de números de NF-e de uma série, quando esses números não foram utilizados e não serão mais emitidos. ```php sefazInutiliza($nSerie, $nIni, $nFin, $xJust); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); if ($std->cStat == 102) { // Inutilização homologada — protocola e armazena $xmlProtocolado = \NFePHP\NFe\Complements::toAuthorize($tools->lastRequest, $response); echo "Inutilização autorizada.\n"; } else { echo "Falha [{$std->cStat}]: {$std->xMotivo}\n"; } } catch (\InvalidArgumentException $e) { echo "Parâmetros inválidos: " . $e->getMessage() . "\n"; } ``` --- ## Tools::sefazCadastro — Consulta cadastro de contribuinte Consulta os dados cadastrais de um contribuinte (emitente/destinatário) em uma SEFAZ específica. Aceita CNPJ, CPF ou Inscrição Estadual como filtro. ```php sefazCadastro('SP', '00716345000119'); // Consulta por IE // $response = $tools->sefazCadastro('SP', '', '111111111111'); // Consulta por CPF // $response = $tools->sefazCadastro('SP', '', '', '12345678901'); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); if ($std->cStat == 111 || $std->cStat == 112) { $infCad = $std->infCons->infCad; echo "Razão Social: {$infCad->xNome}\n"; echo "Situação: {$infCad->xSit}\n"; } } catch (\InvalidArgumentException $e) { echo "Erro: " . $e->getMessage() . "\n"; } ``` --- ## Tools::sefazCsc — Gestão do CSC para NFC-e Gerencia o Código de Segurança do Contribuinte (CSC/Token) necessário para emissão de NFC-e (modelo 65). Permite consultar CSC ativos (1), solicitar novo CSC (2) ou revogar o CSC ativo (3). ```php model('65'); try { // 1 = Consultar CSC ativo $response = $tools->sefazCsc(1); // 2 = Solicitar novo CSC // $response = $tools->sefazCsc(2); // 3 = Revogar CSC ativo // $response = $tools->sefazCsc(3); $std = (new \NFePHP\NFe\Common\Standardize($response))->toStd(); echo "cStat: {$std->cStat} - {$std->xMotivo}\n"; } catch (\InvalidArgumentException | \RuntimeException $e) { echo "Erro: " . $e->getMessage() . "\n"; } ``` --- ## Complements::toAuthorize — Protocolação do XML Combina o XML da NF-e (ou evento) com o protocolo de autorização retornado pela SEFAZ, gerando o documento fiscal protocolado (`nfeProc`) pronto para armazenamento e envio ao destinatário. ```php lastRequest ou o XML original) // $responseXml = XML de retorno da SEFAZ com o protocolo $xmlProtocolado = Complements::toAuthorize($requestXml, $responseXml); // O XML resultante contém a tag com a NF-e + protocolo file_put_contents('/storage/nfe/' . $chave . '-nfeProc.xml', $xmlProtocolado); // Para eventos (cancelamento, CC-e, etc.) o resultado contém // Para inutilização o resultado contém ``` --- ## Standardize — Conversão de XML de resposta A classe `Standardize` converte os XMLs de resposta da SEFAZ em `stdClass`, array associativo ou JSON, facilitando o acesso programático aos dados retornados. ```php whichIs(); // Ex: 'retEnviNFe', 'retConsSitNFe', 'retEnvEvento' // Converter para stdClass $obj = $std->toStd(); echo $obj->cStat . ' - ' . $obj->xMotivo . "\n"; // Converter para array associativo $arr = $std->toArray(); echo $arr['cStat'] . "\n"; // Converter para JSON $json = $std->toJson(); $data = json_decode($json, true); echo $data['cStat'] . "\n"; // Validar assinatura e existência de protocolo $valid = $std->validResponseXml($responseXml); ``` --- ## Contingência — Emissão em modo offline A classe `Contingency` gerencia os modos de contingência (SVC-AN, SVC-RS, FS-DA, EPEC, OFF-LINE para NFC-e). Ao ativar, a classe `Tools` ajusta e re-assina automaticamente os XMLs enviados. ```php contingency->activate('SP', 'Sistema SEFAZ SP indisponível'); // IMPORTANTE: salvar $contingenciaJson na base de dados para persistência // Em instâncias futuras da classe, recarregar o estado de contingência $tools->contingency->load($contingenciaJson); // O envio via sefazEnviaLote() irá automaticamente: // 1. Alterar para o código do modo de contingência // 2. Inserir e // 3. Re-assinar o XML (a chave da NF-e será alterada — gravar a nova chave) $xmlsAlterados = []; $response = $tools->sefazEnviaLote([$xmlOriginal], '001', 0, false, $xmlsAlterados); // $xmlsAlterados contém os XMLs modificados para o modo de contingência // Gravar as novas chaves de acesso foreach ($xmlsAlterados as $xmlMod) { $dom = new DOMDocument(); $dom->loadXML($xmlMod); $novaChave = substr($dom->getElementsByTagName('infNFe')->item(0)->getAttribute('Id'), 3); echo "Nova chave em contingência: $novaChave\n"; } // Desativar a contingência $tools->contingency->deactivate(); ``` --- ## Sumário O sped-nfe cobre todos os casos de uso operacionais de um emissor de documentos fiscais eletrônicos no Brasil: emissão de NF-e (modelo 55) e NFC-e (modelo 65) em ambiente de produção e homologação, envio síncrono e assíncrono, todos os eventos fiscais previstos pela legislação (cancelamento, carta de correção, manifestação do destinatário, EPEC, ator interessado, comprovante de entrega, insucesso de entrega, conciliação financeira), consulta de situação, inutilização de numeração, consulta de cadastro de contribuintes, distribuição de documentos fiscais eletrônicos (DF-e) e gerenciamento completo de contingências (SVC-AN, SVC-RS, FS-DA, EPEC e OFF-LINE). Toda a comunicação com a SEFAZ usa SOAP com certificado digital A1 (PFX), e os XMLs são gerados, assinados e validados contra os schemas XSD oficiais da SEFAZ antes do envio. Em termos de integração, a biblioteca é instalada via Composer e segue namespaces PSR-4 (`NFePHP\NFe\`). O fluxo padrão de integração com um sistema ERP ou PDV consiste em: (1) montar o JSON de configuração e carregar o certificado PFX, instanciando `Tools`; (2) usar `Make` para construir o XML da NF-e/NFC-e programaticamente a partir de dados do sistema; (3) assinar o XML com `Signer` do pacote sped-common; (4) enviar via `sefazEnviaLote` e processar a resposta; (5) protocolar com `Complements::toAuthorize` e armazenar o XML definitivo; (6) converter respostas da SEFAZ com `Standardize` para facilitar a extração de dados. Para impressão do DANFE, use o pacote complementar `nfephp-org/sped-da`; para envio de e-mail fiscal, use `nfephp-org/sped-mail`.