Página 2 de 2

Usando classe para assinar nfse - São Paulo

Enviado: 21 Mar 2023 10:00
por malcarli
Mestre,

ShellExecute(0, [open], [AssinaRPS.EXE],,, 1)

este trecho o amigo Eduardo, fez esse executável em c# para assinar a tag assinatura, como não consegui nenhuma informação de como converter para harbour estou usando por eqto este executável externamente.

Já juntei um pouco conforme o código abaixo

Código: Selecionar todos


#include "hbclass.ch"

*#define WS_NFE_STATUSSERVICO         17   /// não descobri onde usar

#define WS_CANCELAMENTONFE           1
#define WS_ENVIOLOTERPS              2
#define WS_ENVIORPS                  3    /// acrescentei
#define WS_CONSULTARPS               4    /// acrescentei

#define WS_AMBIENTE_HOMOLOGACAO      "2"  /// sp, foz não tem mais o ambiente de homologação
#define WS_AMBIENTE_PRODUCAO         "1"

#ifndef XML_UTF8
   #define XML_UTF8                     [<?xml version="1.0" encoding="UTF-8"?>]
#endif

CREATE CLASS NfseClasse   //// mudei nome pois estava conflitando com a sefazclass  

   /* configuração */
   VAR    cAmbiente        INIT WS_AMBIENTE_PRODUCAO
   VAR    cUF              INIT "SP"                    // Modificada conforme método
   VAR    cCertificado     INIT ""                      // Nome do certificado
   VAR    nTempoEspera     INIT 7                       // intervalo entre envia lote e consulta recibo
   /* XMLs de cada etapa */
   VAR    cXmlDocumento    INIT ""                      // O documento oficial, com ou sem assinatura, depende do documento
   VAR    cXmlEnvio        INIT ""                      // usado pra criar/complementar XML do documento
   VAR    cXmlSoap         INIT ""                      // XML completo enviado pra Sefaz, incluindo informações do envelope
   VAR    cXmlRetorno      INIT "Erro Desconhecido"     // Retorno do webservice e/ou rotina
   VAR    cXmlProtocolo    INIT ""                      // XML protocolo (obtido no consulta recibo e/ou envio de outros docs)
   VAR    cXmlAutorizado   INIT ""                      // XML autorizado, caso tudo ocorra sem problemas
   VAR    cStatus          INIT Space(3)                // Status obtido da resposta final da Fazenda
   /* uso interno */
   VAR    cSoapService     INIT ""                      // webservice Serviço
   VAR    cSoapAction      INIT ""                      // webservice Action
   VAR    cSoapURL         INIT ""                      // webservice Endereço

*** acrescentei
   VAR    lComUri          INIT .F.                     // Não tem tag cUri
   VAR    cPassword        INIT ""                      // Senha de arquivo PFX
   VAR    cMotivo          INIT ""                      // Motivo constante no Recibo
   VAR    nCodigoMunicipio INIT 3550308                 // São Paulo

   METHOD EnvioLoteRPS( cXml, cCertificado, cAmbiente )
 ***** acrescentei
   METHOD EnvioRPS( cXml, cCertificado, cAmbiente )
   METHOD CancelamentoRPS( cXml, cCertificado, cAmbiente )
   METHOD ConsultaRPS( cXml, cCertificado, cAmbiente )

   /* Uso interno */
   METHOD Setup( cCertificado, cAmbiente, nWsServico )
   METHOD XmlSoapEnvelope()
   METHOD XmlSoapPost()
   METHOD MicrosoftXmlSoapPost()
***** acrescentei
   METHOD AssinaXml(cXml)
   METHOD Gera_Chave_SHA1(cString)

   ENDCLASS

** alterei foz máximo 20 rps por lote
METHOD EnvioLoteRPS( cXml, cCertificado, cAmbiente ) CLASS NfseClasse

   ::Setup( cCertificado, cAmbiente, WS_ENVIOLOTERPS )
   ::cXmlEnvio := ::AssinaXml( cXml )
   ::cXmlEnvio := ::cXmlDocumento
   ::XmlSoapPost()

   RETURN ::cXmlRetorno

** acrescentei
METHOD EnvioRPS( cXml, cCertificado, cAmbiente ) CLASS NfseClasse
   ::Setup( cCertificado, cAmbiente, WS_ENVIORPS )
   ::cXmlEnvio := ::AssinaXml( cXml )
   ::cXmlEnvio := ::cXmlDocumento
   ::XmlSoapPost()

   RETURN ::cXmlRetorno

** acrescentei
METHOD CancelamentoRPS( cXml, cCertificado, cAmbiente ) CLASS NfseClasse
   ::Setup( cCertificado, cAmbiente, WS_CANCELAMENTONFE )

   ::cXmlEnvio := ::AssinaXml( cXml )
   ::cXmlEnvio := ::cXmlDocumento
   ::XmlSoapPost()

   RETURN ::cXmlRetorno

** acrescentei
METHOD ConsultaRPS( cXml, cCertificado, cAmbiente ) CLASS NfseClasse
   ::Setup( cCertificado, cAmbiente, WS_CONSULTARPS )
   ::cXmlEnvio := ::AssinaXml( cXml )
   ::cXmlEnvio := ::cXmlDocumento
   ::XmlSoapPost()

   RETURN ::cXmlRetorno

METHOD Setup( cCertificado, cAmbiente, nWsServico ) CLASS NfseClasse
   LOCAL nPos, aSoapList

   If ::nCodigoMunicipio == 3550308 // sp
      aSoapList := { ;
                   { WS_CANCELAMENTONFE, "CancelamentoNFe", "http://www.prefeitura.sp.gov.br/nfe/ws/cancelamentoNFe", "https://nfe.prefeitura.sp.gov.br/ws/lotenfe.asmx" }, ;
                   { WS_ENVIOLOTERPS   , "EnvioLoteRPS"   , "http://www.prefeitura.sp.gov.br/nfe/ws/envioLoteRPS"   , "https://nfe.prefeitura.sp.gov.br/ws/lotenfe.asmx" }, ;
                   { WS_ENVIORPS       , "EnvioRPS"       , "http://www.prefeitura.sp.gov.br/nfe/ws/envioRPS"       , "https://nfe.prefeitura.sp.gov.br/ws/lotenfe.asmx" }, ;
                   { WS_CONSULTARPS    , "ConsultaLote"   , "http://www.prefeitura.sp.gov.br/nfe/ws/consultaLote"   , "https://nfe.prefeitura.sp.gov.br/ws/lotenfe.asmx" } }
   ElseIf ::nCodigoMunicipio == 4108304 // foz do iguaçu
      aSoapList := { ;
                   { WS_CANCELAMENTONFE, "CancelamentoNFSE"           , "http://tempuri.org/CancelamentoNFSE"           , "http://nfse.pmfi.pr.gov.br/nfsews/nfse.asmx" }, ;
                   { WS_ENVIOLOTERPS   , "EnviaLotesParaProcessamento", "http://tempuri.org/EnviaLotesParaProcessamento", "http://nfse.pmfi.pr.gov.br/nfsews/nfse.asmx" }, ;
                   { WS_ENVIORPS       , "RecebeLoteRPS"              , "http://tempuri.org/RecebeLoteRPS"              , "http://nfse.pmfi.pr.gov.br/nfsews/nfse.asmx" }, ;
                   { WS_CONSULTARPS    , "ConsultarLoteRPS"           , "http://tempuri.org/ConsultarLoteRPS"           , "http://nfse.pmfi.pr.gov.br/nfsews/nfse.asmx" } }
   Endif

*  sp e foz, apesar de ter endereço de homologação, estão desativados, conforme o suporte dos mesmos

   ::cCertificado := iif( cCertificado == Nil, ::cCertificado, cCertificado )
   ::cAmbiente    := iif( cAmbiente == Nil, ::cAmbiente, cAmbiente )   

   IF nWsServico == Nil
      RETURN Nil
   ENDIF
   IF ( nPos := hb_AScan( aSoapList, { | oElement | oElement[ 1 ] == nWsServico } ) ) != 0
      ::cSoapService := aSoapList[ nPos, 2 ]
      ::cSoapAction  := aSoapList[ nPos, 3 ]
      ::cSoapURL     := aSoapList[ nPos, 4 ]

   ENDIF
   RETURN Nil

METHOD XmlSoapPost() CLASS NfseClasse

   DO CASE
   CASE Empty( ::cSoapURL )
      ::cXmlRetorno := "Erro SOAP: Não há endereço de webservice"
      RETURN Nil
   CASE Empty( ::cSoapService )
      ::cXmlRetorno := "Erro SOAP: Não há nome do serviço"
      RETURN Nil
   CASE Empty( ::cSoapAction )
      ::cXmlRetorno := "Erro SOAP: Não há endereço de SOAP Action"
      RETURN Nil
   ENDCASE
   ::XmlSoapEnvelope()
   ::MicrosoftXmlSoapPost()
   IF Upper( Left( ::cXmlRetorno, 4 ) )  == "ERRO"
      RETURN Nil
   ENDIF

   RETURN Nil

METHOD XmlsoapEnvelope() CLASS NfseClasse
   ::cXmlsoap    := XML_UTF8
   ::cXmlsoap    += [<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ]
   ::cXmlsoap    +=       [xmlns:xsd="http://www.w3.org/2001/XMLSchema" ]
   ::cXmlsoap    +=       [xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">]
   ::cXmlSoap    +=    [<soap12:Body>]

   If ::nCodigoMunicipio == 3550308 // sp
      ::cXmlSoap    +=       [<] + ::cSoapService + [Request xmlns="http://www.prefeitura.sp.gov.br/nfe">]
      ::cXmlSoap    +=          [<VersaoSchema>1</VersaoSchema>]
      ::cXmlSoap    +=          [<MensagemXML>]
      ::cXmlSoap    +=             "<![CDATA[ " + ::cXmlEnvio + " ]]>"
      ::cXmlSoap    +=          [</MensagemXML>]
      ::cXmlSoap    +=       [</] + ::cSoapService + [Request>]
   ElseIf ::nCodigoMunicipio == 4108304 // foz do iguaçu
      ::cXmlSoap    +=       [<] + ::cSoapService + [ xmlns="http://tempuri.org/">]
      ::cXmlSoap    +=             [<xml>] + ::cXmlEnvio + [</xml>]
      ::cXmlSoap    +=       [</] + ::cSoapService + [>]
   Endif

   ::cXmlSoap    +=   [</soap12:Body>]
   ::cXmlSoap    += [</soap12:Envelope>]
   RETURN Nil

METHOD MicrosoftXmlSoapPost() CLASS NfseClasse

   LOCAL oServer, nCont, cRetorno
   LOCAL cSoapAction

   cSoapAction := ::cSoapAction
   BEGIN SEQUENCE WITH __BreakBlock()
      ::cXmlRetorno := "Erro: Criando objeto MSXML2.ServerXMLHTTP"
      oServer := win_OleCreateObject( "MSXML2.ServerXMLHTTP" )
      ::cXmlRetorno := "Erro: No uso do objeto MSXML2.ServerXmlHTTP"
      IF ::cCertificado != Nil
         oServer:setOption( 3, "CURRENT_USER\MY\" + ::cCertificado )
      ENDIF
      ::cXmlRetorno := "Erro: Na conexão com webservice " + ::cSoapURL
      oServer:Open( "POST", ::cSoapURL, .F. )
      oServer:SetRequestHeader( "SOAPAction", cSoapAction )
      oServer:SetRequestHeader( "Content-Type", "application/soap+xml; charset=utf-8" )
      oServer:Send( ::cXmlSoap )
      oServer:WaitForResponse( 500 )
      cRetorno := oServer:ResponseBody
      IF ValType( cRetorno ) == "C"
         ::cXmlRetorno := cRetorno
      ELSEIF cRetorno == Nil
         ::cXmlRetorno := "Erro: Sem retorno do webservice"
      ELSE
         ::cXmlRetorno := ""
         FOR nCont = 1 TO Len( cRetorno )
            ::cXmlRetorno += Chr( cRetorno[ nCont ] )
         NEXT
      ENDIF
   ENDSEQUENCE
   IF "<soap:Body>" $ ::cXmlRetorno .AND. "</soap:Body>" $ ::cXmlRetorno
      ::cXmlRetorno := XmlNode( ::cXmlRetorno, "soap:Body" ) // hb_UTF8ToStr()
   ELSEIF "<soapenv:Body>" $ ::cXmlRetorno .AND. "</soapenv:Body>" $ ::cXmlRetorno
      ::cXmlRetorno := XmlNode( ::cXmlRetorno, "soapenv:Body" ) // hb_UTF8ToStr()
   ELSE
      ::cXmlRetorno := "Erro SOAP: XML retorno não contém soapenv:Body " + ::cXmlRetorno
   ENDIF

   RETURN Nil

**************** acrescentei 
METHOD AssinaXml(cXml) CLASS NfseClasse
   ::cXmlDocumento:= cXml
   ::cXmlRetorno  := CapicomAssinaXml( @::cXmlDocumento, ::cCertificado,,::cPassword, ::lComUri )

   IF ::cXmlRetorno != "OK"
      ::cStatus := "999"
      ::cMotivo := ::cXmlRetorno
      ::cXmlRetorno := [<erro text="] + "*erro* " + ::cXmlRetorno + ["</erro>]
   ENDIF
RETURN ::cXmlRetorno

METHOD Gera_Chave_SHA1(cString) Class NfseClasse
   Local nHandle:= Fcreate([StringParaAssinar.txt]), cRet:= []

*  Fwrite(nHandle, ::ohbNFe:cSerialCert + hb_OsNewLine() + cString)
   Fwrite(nHandle, Upper([4b8db3b9d22a50b5]) + hb_OsNewLine() + cString)
   Fclose(nHandle)
   ShellExecute(0, [open], [AssinaRPS.EXE],,, 1)

   cRet:= Hb_Memoread([StringAssinada.txt])
   cRet:= Strtran(cRet, CHR(13), [])
   cRet:= Strtran(cRet, CHR(10), [])

   Ferase([StringParaAssinar.txt])
   Ferase([StringAssinada.txt])
Return(cRet)

********************* Retira Acentos e Letras de uma String ********************
Function fRetiraAcento(cStr)
   Local aLetraCAc:= {[Á],[À],[Ä],[Ã],[Â],[É],[È],[Ë],[Ê],[&],[Í],[Ì],[Ï],[Î],[Ó],[Ò],[Ö],[Õ],[Ô],[Ú],[Ù],[Ü],[Û],[Ç],[Ñ],[Ý],[á],[à],[ä],[ã],[â],[é],[è],[ë],[ƒ],[ê],[í],[ì],[ï],[î],[ó],[ò],[ö],[õ],[ô],[ú],[ù],[ü],[û],[ç],[ñ],[ý],[ÿ],[º] ,[ª] ,[‡],[Æ],[¡],[£],[ÿ],[ ],[á],[ ] ,[ ],[ ],[‚],[ˆ],[“],[¢],[…],[°],[A³],[A§],[Au],[Ai],[A©],[Ao.]}
   Local aLetraSAc:= {[A],[A],[A],[A],[A],[E],[E],[E],[E],[E],[I],[I],[I],[I],[O],[O],[O],[O],[O],[U],[U],[U],[U],[C],[N],[Y],[a],[a],[a],[a],[a],[e],[e],[e],[a],[e],[i],[i],[i],[i],[o],[o],[o],[o],[o],[u],[u],[u],[u],[c],[n],[y],[y],[o.],[a.],[c],[a],[i],[u],[a],[a],[a],[E ],[a],[ ],[e],[e],[o],[o],[a],[],[o],[c],[a],[a],[e],[u]}, i
   hb_Default(@cStr, [])

   For i:= 1 To Len(aLetraCAc)
       cStr:= StrTran(cStr, aLetraCAc[i], aLetraSAc[i])
   Next
Return (cStr)
********************* Fim da Função Retira Acentos e Letras de uma String ******

Usando classe para assinar nfse - São Paulo

Enviado: 21 Mar 2023 10:07
por JoséQuintas
malcarli escreveu: como não consegui nenhuma informação de como converter para harbour estou usando por eqto este executável externamente.
Geralmente é assinado um bloco do XML.
No caso da NFE, é o bloco com Id=

Lembro no caso do lote de RPS, que eram duas (ou mais) assinaturas:
CADA nota tinha assinatura, e no final o lote também era assinado.
Deixei a rotina de assinatura preparada pra isso, mas... não está automática, teria que assinar cada nota, juntar tudo, e depois assinar o lote.
Nunca recebi retorno de quem precisou, se tudo deu certo.

Usando classe para assinar nfse - São Paulo

Enviado: 21 Mar 2023 10:23
por malcarli
Mestre, não é essa situação. Estou enviando somente uma nota por vez.

Veja o exemplo da tag assinatura:

é uma string montada com algumas informações:

37925504A 00000000102620230321TNN00000000000010000000000000000002919232332624000100

depois de assindada vira isso

<Assinatura>dhfhixJYMyGgZWUqH7zPvboxs9NbcKkyyKO46qKCXbT05HGyZkoyR7KdTX7KJ4m3YljMIzq00hZ2fjUC0HRdnBnDzGYllgzMTew+faUjDCwJlG/0y8ZfVkIFOOF2tsWklwu32lJ2vhhdtqHTTERFJUs0QItIsZz/jdq1BCYEXlCle7zF2ZZhkUzCIbo7U4SjAmNlx8vcsEJCwVGAlML3dEArbYgi0GfK58LsWJEBwK8/Iont24kqvaaQiahyJWXSLPrK/L4u2bEwO37Xp/wo8H8GAFIiNLMLvBby3zJlUu8IiuJm8SP4bg+A4hB0oJ0YuUH6ra52RFk59IRrq/1G2w==</Assinatura>


A tag assinatura é composta pelo seguinte conteúdo:

************************COLOCAR AQUI A CHAVE PARA COMPARAR PARA VER SE ESTÁ CERTO*********************

2º - Converta a cadeia de caracteres ASCII para bytes.
3º - Gere o HASH (array de bytes) utilizando SHA1.
4º - Assine o HASH (array de bytes) utilizando RSA-SHA1.
ATENÇÃO! Na maioria das linguagens de programação, os passos 3 e 4 são feitos através de uma única função. Verifique a documentação de sua linguagem para evitar assinar um hash de um hash.
37925504A 00000000085320230201TNN00000000000010000000000000000002919112345678000199
58402454NF 00000000001620180607TNN00000000001000000000000000000003654214675166000112
31000000OL03 00000000000120070103TNN00000000205000000000000050000002658100013167474254
+-------+----+-----------+-------++++--------------+--------------+----++-------------++-------------+
123456781234512345678901212345678111123456789012345123456789012345123451123456789012341123456789012341
+-------+----+-----------+-------++++--------------+--------------+----++-------------++-------------+
| | | | |||| | | || || |
A B C D EFGH I J KL MN O
+------------------------------------------+-------------+
| A-Inscrição Municipal do Prestador | 8 dígitos |
| B-Série do RPS | 5 diditos |
| C-Número do RPS | 12 dígitos |
| D-Data de Emissão do RPS | 8 dígitos |
| E-Tipo de Tributação do RPS | 1 dígito |
| F-Status do RPS | 1 dígito |
| G-ISS Retido | 1 dígito |
| H-Valor dos Serviços | 15 dígitos |
| I-Valor das deduções | 15 dígitos |
| J-Código do Serviço Prestado | 5 dígitos |
| K-Indicador de CPF/CNPJ do tomador | 1 dígito |
| L-CPF/CNPJ do tomador | 14 dígitos |
| M-Indicador de CPF/CNPJ do Intermediário | 1 dígito |
| N-CPF/CNPJ do Intermediário | 14 dígitos |
| O-ISS Retido Intermediário | 1 dígito |
+------------------------------------------+-------------+
| TOTAL DE DÍGITOS | 102 dígitos |
+------------------------------------------+-------------+

2º - Converta a cadeia de caracteres ASCII para bytes.
3º - Gere o HASH (array de bytes) utilizando SHA1.
4º - Assine o HASH (array de bytes) utilizando RSA-SHA1.
ATENÇÃO! Na maioria das linguagens de programação, os passos 3 e 4 são feitos através de uma única função. Verifique a documentação de sua linguagem para evitar assinar um hash de um hash.