Função em AdvPL para baixar XML de nota direto do SEFAZ

Olá pessoal…

Hoje vou mostrar uma função que baixa o XML de uma NFe direto do Sefaz utilizando um WebService.

Essa função, faz a conexão com o WebService e efetua o download do XML para dentro de uma pasta.

Ela foi desenvolvida por Súlivan Simões (o e-Mail dele consta na documentação da função abaixo). É necessário também criar alguns parâmetros na SX6, que estão documentados na função abaixo. Gostaria de agradecer ao Súlivan por disponibilizar o fonte, e autorizar o compartilhamento dele aqui no Terminal de Informação.

Caso ocorram erros de certificado CA, veja na documentação, os links disponíveis para tirar a dúvida.

Abaixo o código fonte desenvolvido:

#Include "Protheus.ch"
#Include "ApWebSrv.ch"
#Include "TopConn.ch"

/*/{Protheus.doc} zBxXML
Faz download do arquivo XML do site da sefaz usando a Chave da nota
@author Súlivan Simões Silva - Email: sulivansimoes@gmail.com
@since  08/03/2019
@version 1.0
@param cChaveNFe, caracter, chave da NFe à ser baixada da Sefaz 
@example zBxXML
@type function
@see Abaixo os links usados como referência para montagem da função
		Link 01 - http://tdn.totvs.com/display/tec/Classe+TWsdlManager
		Link 02 - http://tdn.totvs.com/display/tec/SOAP+1.1+e+SOAP+1.2
		Link 03 - http://tdn.totvs.com/display/tec/XmlParser		        
		Link 04 - http://www.nfe.fazenda.gov.br/portal/exibirArquivo.aspx?conteudo=iGDFY8YBs28=
	Dúvidas quanto a Parse:
		Link 01 - https://centraldeatendimento.totvs.com/hc/pt-br/articles/360022658731-MP-ADVPL-Peer-certificate-cannot-be-authenticated-with-given-CA-certificates
		Link 02 - http://tdn.totvs.com/display/tec/Acesso+a+Web+Services+que+exigem+certificados+de+CA
@obs Documentação feita e adaptações por Daniel Atilio
	Os parâmetros que devem ser criados na base são:
		MV_X_BXUF  - Código do Estado conforme IBGE ( https://www.oobj.com.br/bc/article/quais-os-c%C3%B3digos-de-cada-uf-no-brasil-465.html )
		MV_X_BXAMB - Indica o tipo de Ambiente, 1 = Produção, 2 = Homologação
		MV_X_BXCA  - Diretório do certificado "ca", por exemplo: \certs\000001_ca.pem
		MV_X_BXCER - Diretório do certificado "cert", por exemplo: \certs\000001_cert.pem
		MV_X_BXKEY - Diretório do certificado "key", por exemplo: \certs\000001_key.pem
		MV_X_BXPSW - Senha do Certificado
		MV_X_BXPRO - Tipo do Protocolo, 0=Descobre sozinho; 1=TLSv1; 2=SSLv2; 3=SSLv3
		MV_X_BXDIR - Diretório que será gerado os arquivos XML
@example Basta chamar a função passando a chave da nfe
	u_zBxXML("CHAVE_AAAAAAAAAAAAAAAAAAAAAAA")
	u_zBxXML("CHAVE_BBBBBBBBBBBBBBBBBBBBBBB")
	u_zBxXML("CHAVE_CCCCCCCCCCCCCCCCCCCCCCC")
/*/

User Function zBxXML(cChaveNFe)
	Local aArea	    := GetArea()
	Local lRet      := .T.
	Local cURL	    := "https://www1.nfe.fazenda.gov.br/NFeDistribuicaoDFe/NFeDistribuicaoDFe.asmx?WSDL"
	Local cMsg      := ""
	Local oWsdl     := Nil
	Local cMsgRet   := ""
	Local cError    := ""
	Local cWarning  := ""
	Local cXmlGZip  := ""
	Local cArqXML   := ""
	Local cUfAutor  := SuperGetMV("MV_X_BXUF",  .F., "35")
	Local cTpAmb    := SuperGetMV("MV_X_BXAMB", .F., "1")
	Local cCNPJEmp  := Alltrim(SM0->M0_CGC)
	Local lContinua := .T.
	Private oXmlDocZip

	//Instância a classe, setando as parâmetrizações necessárias
	oWsdl := TWsdlManager():New()
	oWsdl:cSSLCACertFile := SuperGetMV("MV_X_BXCA",  .F., "\certs\000001_ca.pem")
	oWsdl:cSSLCertFile   := SuperGetMV("MV_X_BXCER", .F., "\certs\000001_cert.pem")
	oWsdl:cSSLKeyFile    := SuperGetMV("MV_X_BXKEY", .F., "\certs\000001_key.pem")
	oWsdl:cSSLKeyPwd     := SuperGetMV("MV_X_BXPSW", .F., "senha")
	oWsdl:nSSLVersion    := SuperGetMV("MV_X_BXPRO", .F., "0")
	oWsdl:nTimeout       := 120

	//Tenta fazer o Parse da URL
	lRet := oWsdl:ParseURL(cURL)
	If ! lRet 
		ConOut("[u_zBxXML] - Erro ParseURL: " + oWsdl:cError)
		lContinua := .F.
	EndIf
	
	//Se for continuar o processamento
	If lContinua
	
		//Tenta definir a operação
		lRet := oWsdl:SetOperation("nfeDistDFeInteresse")
		If ! lRet 
			ConOut("[u_zBxXML] - Erro SetOperation: " + oWsdl:cError)
			lContinua := .F.
		EndIf
	EndIf
	
	//Se for continuar
	If lContinua
		//Monta a mensagem que será enviada
		cMsg := '<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">'                        + CRLF
		cMsg += '	<soapenv:Header/>'                                                                              + CRLF
		cMsg += '    <soapenv:Body>'                                                                                + CRLF
		cMsg += '        <nfeDistDFeInteresse xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe">'  + CRLF
		cMsg += '	            <nfeDadosMsg>'                                                                      + CRLF
		cMsg += '					<distDFeInt xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.01">'          + CRLF
		cMsg += '						<tpAmb>'+cTpAmb+'</tpAmb>'                                                  + CRLF
		cMsg += ' 					<cUFAutor>'+cUfAutor+'</cUFAutor>'                                              + CRLF
		cMsg += '						<CNPJ>'+cCNPJEmp+'</CNPJ>'                                                  + CRLF
		cMsg += '  					<consChNFe>'                                                                    + CRLF
		cMsg += '   						<chNFe>'+alltrim(cChaveNFe)+'</chNFe>'                                  + CRLF
		cMsg += '  					</consChNFe>'                                                                   + CRLF
		cMsg += '					</distDFeInt>'                                                                  + CRLF
		cMsg += '	            </nfeDadosMsg>'                                                                     + CRLF
		cMsg += '	        </nfeDistDFeInteresse>'                                                                 + CRLF
		cMsg += '	    </soapenv:Body>'                                                                            + CRLF
		cMsg += '	</soapenv:Envelope>'                                                                            + CRLF
		
		//Envia uma mensagem SOAP personalizada ao servidor
		lRet := oWsdl:SendSoapMsg(cMsg)
		If ! lRet 
			ConOut("[u_zBxXML] - Erro SendSoapMsg: " + oWsdl:cError)
			ConOut("[u_zBxXML] - Erro SendSoapMsg FaultCode: " + oWsdl:cFaultCode)
			lContinua := .F.
		EndIf
	EndIf

	//Se for continuar
	If lContinua
		//Pega a resposta do SOAP
		cMsgRet := oWsdl:GetSoapResponse()
						   
		//Transforma a resposta em um objeto
		oXmlDocZip := XmlParser(cMsgRet, "_", @cError, @cWarning)
		
		//Se existir Warning, mostra no console.log
		If ! Empty(cWarning)
			ConOut("[u_zBxXML] - Alerta cWarning: " + cWarning)
		EndIf
		
		//Se houve erro, não permitirá prosseguir
		If ! Empty(cError)
			ConOut("[u_zBxXML] - Erro cError: " + cError)
			lContinua := .F.
		EndIf
	EndIf

	//Se for continuar
	If lContinua
		//Se a tag DocZip existir (for diferente de Undefinied)
		If (Type("oXmlDocZip:_SOAP_ENVELOPE:_SOAP_BODY:_NFEDISTDFEINTERESSERESPONSE:_NFEDISTDFEINTERESSERESULT:_RETDISTDFEINT:_LOTEDISTDFEINT:_DOCZIP") != "U")

			//Pega tag que contém XML em zip
			cXmlGZip := oXmlDocZip:_SOAP_ENVELOPE:_SOAP_BODY:_NFEDISTDFEINTERESSERESPONSE:_NFEDISTDFEINTERESSERESULT:_RETDISTDFEINT:_LOTEDISTDFEINT:_DOCZIP:TEXT
			
			//Gera arquivo XML
			cArqXML := fGeraXML(cXmlGZip, cChaveNFe)
		else
			conout("[u_zBxXML] - Ocorreu algum problema no momento de baixar o arquivo da sefaz!")
		endif
	EndIf

	RestArea(aArea)
Return cArqXML

/*-------------------------------------------------------------------------------*
 | Func:  fGeraXML                                                               |
 | Desc:  Função para gerar o arquivo XML em uma pasta                           |
 *-------------------------------------------------------------------------------*/

Static Function fGeraXML(cConteudo, cChave)
	Local aArea   	 := getArea()
	Local lRet    	 := .T. 
	Local cArquivo	 := cChave + ".xml"
	Local cDiretorio := Alltrim(SuperGetMV("MV_X_BXDIR", .F., "C:\TOTVS\XML\"))
	Local nTamanho	 := 0
	Local cUnXML  	 := "" 
	Local cDecode64  := ""
	Local cArqFull   := ""
	Local lHouveErro := .F.
	
	//Se o último caracter do diretório não for \, será barra \
	If SubStr(cDiretorio, Len(cDiretorio), 1) != "\"
		cDiretorio += "\"
	EndIf
	
	//Define o caminho final do arquivo
	cArqFull := cDiretorio + cArquivo
	
	//Pega o tamanho e descriptografa o conteúdo
	nTamanho  := Len(cConteudo)
	cDecode64 := Decode64(cConteudo)
	lRet      := GzStrDecomp(cDecode64, nTamanho, @cUnXML)
     
    //Se deu certo
	If lRet
		
		//Se o diretório não existir, cria
		If ! ExistDir(cDiretorio)
			MakeDir(cDiretorio)
		EndIf
		
		//Cria o arquivo com o conteúdo
		lRet := MemoWrite(cDiretorio+cArquivo, cUnXML)
		
		//Se houve falha, mostra mensagem no console.log
		If ! lRet
			ConOut("[u_zBxXML][fGeraXML] - Não foi possivel criar o arquivo: " + cArqFull)
			lHouveErro := .T.
		EndIf
	
	//Se não deu certo, mostra mensagem no console.log
	Else
		ConOut("[u_zBxXML][fGeraXML] - Houve algum erro na descompactação do arquivo!")
		lHouveErro := .T.
	EndIf

	//Se houve erro, zera o nome do arquivo para retornar em branco
	If lHouveErro
		cArqFull := ""
	EndIf

	RestArea(aArea)
Return cArqFull
Esses e outros códigos, estão disponíveis gratuitamente no nosso GitHub, acesse em github.com/dan-atilio/AdvPL.

Bom pessoal, por hoje é só.

Abraços e até a próxima.

Dan_Atilio
Analista e desenvolvedor de sistemas. Técnico em Informática pelo CTI da Unesp. Graduado em Banco de Dados pela Fatec Bauru. Entusiasta de soluções Open Source e blogueiro nas horas vagas.

9 Responses

  1. Fco. C. Dantas q disse:

    O Endereço da URL não é valido o que impossibilita a validação.
    Eu não achei nenhum outro endereço na net.
    Se tiver um endereço com o WSDL valido, seria muto bem vindo.

  2. Súlivan Simões disse:

    Bom dia Dantas, tudo certo?

    O endereço da URL presente na função, é válido sim. Ela está funcionando corretamente.

    Como conversamos via Skype. O problema era que a você não tinha copiado a pasta Certs do TSS para a Protheus_data, e isso acarretava erro no momento de fazer o parse.

    Feito isso, a rotina funcionou!

    Atenciosamente.

  3. Dayvid Nogueira disse:

    Bom dia,
    Primeiramente gostaria de parabenizar pelo conteúdo da função e de todo o site, estão fazendo um excelente trabalho!
    Estou necessitando dessa função, pois desenvolvi uma função que busca as informações do XML pelo arquivo txt, porem o arquivo precisa estar em um diretório.
    Estou com o seguinte erro em relação a função descrita, depois que passa pelo método oWsdl:SendSoapMsg(cMsg)

    Value = “A WSDL exception occurred at0:0 WsdlParser Exception : Error sending SOAP message: Peer certificate cannot be authenticated with given CA certificates ”

    Obrigado pela Atenção.

  4. Súlivan Simões disse:

    Bom dia Dayvid , beleza?

    Este é erro pode ser comum de acontecer.

    Siga os passos dos links para corrigi-lo:

    Dúvidas quanto a Parse:
    Link 01 – https://centraldeatendimento.totvs.com/hc/pt-br/articles/360022658731-MP-ADVPL-Peer-certificate-cannot-be-authenticated-with-given-CA-certificates

    Link 02 – http://tdn.totvs.com/display/tec/Acesso+a+Web+Services+que+exigem+certificados+de+CA

    Atenciosamente.

  5. Há algum limite de conexões e notas que podemos baixar no sefaz?

  6. Súlivan Simões disse:

    Boa noite Jorge,

    Desconheço tal limite. acredito que não tenha.

    Atenciosamente.

    • Dan_Atilio disse:

      Complementando a resposta do Súlivan, Jorge.
      Desconheço que o SEFAZ tenha limite, talvez o que você encontre limites seja no próprio AdvPL, como no número de licenças (se você for executar mais de uma vez).
      Abraços galera, e obrigado Súlivan.

Deixe uma resposta