/* * This file is part of Treeunfe DFe. * * Treeunfe DFe is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Treeunfe DFe is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Treeunfe DFe. If not, see . */ /** * Serviço de TESTE de autorização NFS-e no padrão Nota Fiscal Paulistana (NFP) * REFORMA TRIBUTÁRIA 2026 - Versão 2.x * * IMPORTANTE: Este é o serviço de TESTE disponível para adaptação dos sistemas. * Usa TesteEnvioLoteRPS em vez de EnvioLoteRPS. * Este método NÃO substitui os RPS por NF-e reais, apenas valida. * * WebService: https://nfews.prefeitura.sp.gov.br/lotenfe.asmx * Operação: TesteEnvioLoteRPS * Namespace: http://www.prefeitura.sp.gov.br/nfe * Referência: Manual de Utilização do Web Service de NFS-e SP (v3.3.5) - 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_SPTesteAutorizacaoService_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_TesteEnvioLoteRPS', axios, saveFiles, gerarConsulta, ); this.municipio = 'SP'; } protected getSoapAction(): string { return 'testeenvio'; } protected getSoapVersion(): 1.1 | 1.2 { return 1.1; } protected getRequestWrapper(): string | undefined { return 'TesteEnvioLoteRPSRequest'; } 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(''); // NÃO incluir xmlns aqui pois ele será herdado do Request wrapper 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}`; // Assina o PedidoEnvioLoteRPS completo let xmlAssinado = this.xmlBuilder.assinarXML(xmlPedido, 'PedidoEnvioLoteRPS'); // Remove o atributo Id que xml-crypto adiciona (não permitido pelo XSD) 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; } // Extrai o RetornoXML do wrapper Response 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; } // Parse do XML interno 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 Teste: ${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 Teste', { context: 'NFSeNFP_SPTesteAutorizacaoService_V2', responseSize: String(xmlResposta).length, }); const resultado = await this.parsearResposta(String(xmlResposta)); if (resultado.success) { logger.info('Teste de NFS-e Nota Fiscal Paulistana (SP) V2 concluído com sucesso', { context: 'NFSeNFP_SPTesteAutorizacaoService_V2', }); } return resultado; } catch (error: any) { logger.error('Erro no teste de autorização NFS-e Nota Fiscal Paulistana (SP) - V2', error, { context: 'NFSeNFP_SPTesteAutorizacaoService_V2', }); throw error; } } } export default NFSeNFP_SPTesteAutorizacaoService_V2;