/** * TreeUnfe DFe - Biblioteca de Documentos Fiscais Eletrônicos * https://github.com/cteodoro/tree-unfe-dfe * * @fileoverview * Serviço de Autorização NFP SP V2 (Reforma Tributária 2026) */ import { BaseNFSeNFP, Environment, logger, SaveFiles, Utility, XmlBuilder } from '@treeunfe/shared'; import { NFSeNFPAutorizacaoServiceV2Impl } from '@treeunfe/types/interfaces'; import { NFSeNFP_AutorizacaoResponse, NFSeNFP_V2_PedidoEnvioLoteRPS, NFSeNFP_Rps, } from '@treeunfe/types'; import { AxiosInstance } from 'axios'; import { GerarConsultaImpl } from '@treeunfe/types/interfaces'; import xml2js from 'xml2js'; class NFSeNFP_SPAutorizacaoService_V2 extends BaseNFSeNFP implements NFSeNFPAutorizacaoServiceV2Impl { constructor( environment: Environment, utility: Utility, xmlBuilder: XmlBuilder, axios: AxiosInstance, saveFiles: SaveFiles, gerarConsulta: GerarConsultaImpl, ) { super( environment, utility, xmlBuilder, 'NFSe_NFP_V2_EnvioLoteRPS', axios, saveFiles, gerarConsulta, ); this.municipio = 'SP'; } protected getSoapAction(): string { return 'envioLoteRPS'; } protected getSoapVersion(): 1.1 | 1.2 { return 1.1; } protected getRequestWrapper(): string | undefined { return 'EnvioLoteRPSRequest'; } protected getVersaoSchema(): number { return 2; } /** * Serializa um RPS individual para XML - Formato Reforma Tributária 2026 */ private serializarRps(rps: NFSeNFP_Rps): string { const inf = rps.InfRps; const valores = inf.Servico.Valores; const tomador = inf.Tomador ? `${inf.Tomador.IdentificacaoTomador ? `${inf.Tomador.IdentificacaoTomador.CpfCnpj.Cnpj ? `${inf.Tomador.IdentificacaoTomador.CpfCnpj.Cnpj}` : ''}${inf.Tomador.IdentificacaoTomador.CpfCnpj.Cpf ? `${inf.Tomador.IdentificacaoTomador.CpfCnpj.Cpf}` : ''}${inf.Tomador.IdentificacaoTomador.InscricaoMunicipal ? `${inf.Tomador.IdentificacaoTomador.InscricaoMunicipal}` : ''}` : ''}${inf.Tomador.RazaoSocial ? `${inf.Tomador.RazaoSocial}` : ''}${inf.Tomador.Endereco ? `${inf.Tomador.Endereco.Endereco ? `${inf.Tomador.Endereco.Endereco}` : ''}${inf.Tomador.Endereco.Numero ? `${inf.Tomador.Endereco.Numero}` : ''}${inf.Tomador.Endereco.Complemento ? `${inf.Tomador.Endereco.Complemento}` : ''}${inf.Tomador.Endereco.Bairro ? `${inf.Tomador.Endereco.Bairro}` : ''}${inf.Tomador.Endereco.CodigoMunicipio ? `${inf.Tomador.Endereco.CodigoMunicipio}` : ''}${inf.Tomador.Endereco.Uf ? `${inf.Tomador.Endereco.Uf}` : ''}${inf.Tomador.Endereco.Cep ? `${inf.Tomador.Endereco.Cep}` : ''}` : ''}${inf.Tomador.Contato ? `${inf.Tomador.Contato.Telefone ? `${inf.Tomador.Contato.Telefone}` : ''}${inf.Tomador.Contato.Email ? `${inf.Tomador.Contato.Email}` : ''}` : ''}` : ''; const ibscbs = inf.IBSCBS ? `${inf.IBSCBS.cClassTrib ? `${inf.IBSCBS.cClassTrib}` : ''}${inf.IBSCBS.CST ? `${inf.IBSCBS.CST}` : ''}${inf.IBSCBS.vBC !== undefined ? `${inf.IBSCBS.vBC}` : ''}${inf.IBSCBS.pAliqIBS !== undefined ? `${inf.IBSCBS.pAliqIBS}` : ''}${inf.IBSCBS.vIBS !== undefined ? `${inf.IBSCBS.vIBS}` : ''}${inf.IBSCBS.pAliqCBS !== undefined ? `${inf.IBSCBS.pAliqCBS}` : ''}${inf.IBSCBS.vCBS !== undefined ? `${inf.IBSCBS.vCBS}` : ''}` : ''; const xmlRps = `${inf.IdentificacaoRps.Numero}${inf.IdentificacaoRps.Serie}${inf.IdentificacaoRps.Tipo}${inf.DataEmissao}${inf.NaturezaOperacao}${inf.RegimeEspecialTributacao !== undefined ? `${inf.RegimeEspecialTributacao}` : ''}${inf.OptanteSimplesNacional}${inf.IncentivadorCultural}${inf.Status !== undefined ? `${inf.Status}` : ''}${valores.ValorServicos}${valores.ValorDeducoes !== undefined ? `${valores.ValorDeducoes}` : ''}${valores.ValorPis !== undefined ? `${valores.ValorPis}` : ''}${valores.ValorCofins !== undefined ? `${valores.ValorCofins}` : ''}${valores.ValorInss !== undefined ? `${valores.ValorInss}` : ''}${valores.ValorIr !== undefined ? `${valores.ValorIr}` : ''}${valores.ValorCsll !== undefined ? `${valores.ValorCsll}` : ''}${valores.IssRetido}${valores.ValorIss !== undefined ? `${valores.ValorIss}` : ''}${valores.ValorIssRetido !== undefined ? `${valores.ValorIssRetido}` : ''}${valores.OutrasRetencoes !== undefined ? `${valores.OutrasRetencoes}` : ''}${valores.BaseCalculo !== undefined ? `${valores.BaseCalculo}` : ''}${valores.Aliquota !== undefined ? `${valores.Aliquota}` : ''}${valores.ValorLiquidoNfse !== undefined ? `${valores.ValorLiquidoNfse}` : ''}${valores.DescontoIncondicionado !== undefined ? `${valores.DescontoIncondicionado}` : ''}${valores.DescontoCondicionado !== undefined ? `${valores.DescontoCondicionado}` : ''}${inf.Servico.ItemListaServico}${inf.Servico.CodigoCnae ? `${inf.Servico.CodigoCnae}` : ''}${inf.Servico.CodigoTributacaoMunicipio ? `${inf.Servico.CodigoTributacaoMunicipio}` : ''}${inf.Servico.Discriminacao}${inf.Servico.CodigoMunicipio}${inf.Servico.CodigoPais ? `${inf.Servico.CodigoPais}` : ''}${inf.Servico.ExigibilidadeISS}${inf.Servico.MunicipioIncidencia ? `${inf.Servico.MunicipioIncidencia}` : ''}${inf.Servico.NumeroProcesso ? `${inf.Servico.NumeroProcesso}` : ''}${inf.Prestador.Cnpj}${inf.Prestador.InscricaoMunicipal ? `${inf.Prestador.InscricaoMunicipal}` : ''}${tomador}${ibscbs}`; return this.xmlBuilder.assinarXML(xmlRps, 'RPS'); } /** * Gera o corpo SOAP para PedidoEnvioLoteRPS - Reforma Tributária 2026 */ protected gerarXmlCorpo(data: NFSeNFP_V2_PedidoEnvioLoteRPS): string { const cab = data.Cabecalho; const rpsArray = Array.isArray(data.RPS) ? data.RPS : [data.RPS]; const rpsXmlList = rpsArray.map((rps: NFSeNFP_Rps) => this.serializarRps(rps)).join(''); const xmlPedido = `${cab.CPFCNPJRemetente.CNPJ ? `${cab.CPFCNPJRemetente.CNPJ}` : ''}${cab.CPFCNPJRemetente.CPF ? `${cab.CPFCNPJRemetente.CPF}` : ''}${cab.transacao !== undefined ? `${cab.transacao}` : ''}${cab.dtInicio}${cab.dtFim}${cab.QtdRPS}${rpsXmlList}`; let xmlAssinado = this.xmlBuilder.assinarXML(xmlPedido, 'PedidoEnvioLoteRPS'); xmlAssinado = xmlAssinado.replace(/]*)\s+Id="[^"]*"/, '\s+<') .replace(/\n/g, '') .trim(); return xmlAssinado; } /** * Parseia a resposta XML SOAP e extrai a NFS-e gerada ou erros. */ private async parsearResposta(xmlResposta: string): Promise { return new Promise((resolve, reject) => { xml2js.parseString(xmlResposta, { explicitArray: false, ignoreAttrs: false }, (err, result) => { if (err) { reject(new Error(`Erro ao parsear resposta SOAP: ${err.message}`)); return; } try { const body = result?.['soap:Envelope']?.['soap:Body'] || result?.['soap12:Envelope']?.['soap12:Body'] || result?.['s:Envelope']?.['s:Body'] || result?.Envelope?.Body; if (!body) { reject(new Error('Resposta SOAP inválida: Body não encontrado')); return; } const fault = body['soap:Fault'] || body['soap12:Fault'] || body['s:Fault'] || body?.Fault; if (fault) { const faultMsg = fault.faultstring || fault.Reason?.Text || 'Erro SOAP desconhecido'; reject(new Error(`SOAP Fault: ${faultMsg}`)); return; } const responseKey = Object.keys(body).find(k => k.toLowerCase().includes('envioloterpsresponse') || k.toLowerCase().includes('testeenvioloterpsresponse'), ); const responseWrapper = responseKey ? body[responseKey] : body; const retornoXml = responseWrapper?.RetornoXML; if (!retornoXml) { reject(new Error('RetornoXML não encontrado na resposta')); return; } xml2js.parseString(retornoXml, { explicitArray: false, ignoreAttrs: false }, (errInterno, resultInterno) => { if (errInterno) { reject(new Error(`Erro ao parsear RetornoXML: ${errInterno.message}`)); return; } const retornoContent = resultInterno?.RetornoEnvioLoteRPS || resultInterno; const cabecalho = retornoContent?.Cabecalho; if (cabecalho?.Sucesso === 'false' || cabecalho?.Sucesso === false) { const erros = retornoContent?.Erro; resolve({ success: false, erros: erros, xmlRetorno: retornoXml, }); return; } const chaves = retornoContent?.ChaveNFeRPS; const alertas = retornoContent?.Alerta; resolve({ success: true, nfseGerada: chaves, alertas: alertas ? (Array.isArray(alertas) ? alertas : [alertas]) : undefined, xmlRetorno: retornoXml, }); }); } catch (parseError: any) { reject(new Error(`Erro ao processar resposta NFP SP V2: ${parseError.message}`)); } }); }); } public async Exec(data: NFSeNFP_V2_PedidoEnvioLoteRPS): Promise { try { const xmlResposta = await super.Exec(data); logger.info('Resposta recebida do WebService Nota Fiscal Paulistana (SP) - V2', { context: 'NFSeNFP_SPAutorizacaoService_V2', responseSize: String(xmlResposta).length, }); const resultado = await this.parsearResposta(String(xmlResposta)); if (resultado.success) { logger.info('NFS-e autorizada com sucesso (NFP SP V2)', { context: 'NFSeNFP_SPAutorizacaoService_V2', }); } else { logger.warn('Erros retornados pelo WebService Nota Fiscal Paulistana (SP) V2', { context: 'NFSeNFP_SPAutorizacaoService_V2', erros: resultado.erros, }); } return resultado; } catch (error: any) { logger.error('Erro ao executar autorização NFS-e NFP SP V2', error, { context: 'NFSeNFP_SPAutorizacaoService_V2', }); throw error; } } } export default NFSeNFP_SPAutorizacaoService_V2;