/*
* 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;