From 57e502384a40631503d24baaf6704a36621a8f52 Mon Sep 17 00:00:00 2001 From: Danimar Ribeiro Date: Tue, 16 Jun 2015 11:44:39 -0300 Subject: [PATCH] =?UTF-8?q?Iniciado=20classe=20de=20comunica=C3=A7=C3=A3o?= =?UTF-8?q?=20com=20a=20receita?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pytrustnfe/Certificado.py | 24 ++++++++ pytrustnfe/HttpClient.py | 38 +++++++++++++ pytrustnfe/servicos/Comunicacao.py | 54 ++++++++++++++++++ pytrustnfe/servicos/comunicacao.py | 5 -- pytrustnfe/test/XMLs/recibo_envio_1.xml | 13 +++++ pytrustnfe/test/XMLs/recibo_envio_2.xml | 13 +++++ .../test/XMLs/recibo_protocolo_sucesso_1.xml | 21 +++++++ .../test/XMLs/recibo_protocolo_sucesso_2.xml | 21 +++++++ pytrustnfe/test/test_certificado.py | 65 ++++++++++++++++++++++ pytrustnfe/test/test_comunicacao.py | 39 +++++++++++++ 10 files changed, 288 insertions(+), 5 deletions(-) create mode 100644 pytrustnfe/Certificado.py create mode 100644 pytrustnfe/HttpClient.py create mode 100644 pytrustnfe/servicos/Comunicacao.py delete mode 100644 pytrustnfe/servicos/comunicacao.py create mode 100644 pytrustnfe/test/XMLs/recibo_envio_1.xml create mode 100644 pytrustnfe/test/XMLs/recibo_envio_2.xml create mode 100644 pytrustnfe/test/XMLs/recibo_protocolo_sucesso_1.xml create mode 100644 pytrustnfe/test/XMLs/recibo_protocolo_sucesso_2.xml create mode 100644 pytrustnfe/test/test_certificado.py create mode 100644 pytrustnfe/test/test_comunicacao.py diff --git a/pytrustnfe/Certificado.py b/pytrustnfe/Certificado.py new file mode 100644 index 0000000..3d55fa2 --- /dev/null +++ b/pytrustnfe/Certificado.py @@ -0,0 +1,24 @@ +#coding=utf-8 +''' +Created on Jun 16, 2015 + +@author: danimar +''' +import os.path +from OpenSSL import crypto + + +def converte_pfx_pem(caminho, senha): + if not os.path.isfile(caminho): + raise Exception('Certificado não existe') + stream = open(caminho, 'rb').read() + try: + certificado = crypto.load_pkcs12(stream, senha) + + privada = crypto.dump_privatekey(crypto.FILETYPE_PEM, certificado.get_privatekey()) + certificado = crypto.dump_certificate(crypto.FILETYPE_PEM, certificado.get_certificate()) + except Exception as e: + if len(e.message) == 1 and len(e.message[0])==3 and e.message[0][2] == 'mac verify failure': + raise Exception('Senha inválida') + raise + return privada, certificado \ No newline at end of file diff --git a/pytrustnfe/HttpClient.py b/pytrustnfe/HttpClient.py new file mode 100644 index 0000000..52ae183 --- /dev/null +++ b/pytrustnfe/HttpClient.py @@ -0,0 +1,38 @@ +#coding=utf-8 +''' +Created on Jun 16, 2015 + +@author: danimar +''' +from httplib import HTTPSConnection, HTTPResponse + +class HttpClient(object): + + def __init__(self, url, chave_pem, certificado_pem): + self.url = url + self.chave_pem = chave_pem + self.certificado_pem = certificado_pem + + def _headers(self): + return { + u'Content-type': u'application/soap+xml; charset=utf-8', + u'Accept': u'application/soap+xml; charset=utf-8' + } + + def post_xml(self, post, xml): + + conexao = HTTPSConnection(self.url, '443', key_file=self.chave_pem, + cert_file=self.certificado_pem) + + try: + conexao.request(u'POST', post, xml, self._headers()) + response = conexao.getresponse() + if response.status == '200': + return response.read() + except Exception as e: + print str(e) + finally: + conexao.close() + + + \ No newline at end of file diff --git a/pytrustnfe/servicos/Comunicacao.py b/pytrustnfe/servicos/Comunicacao.py new file mode 100644 index 0000000..c46a3b5 --- /dev/null +++ b/pytrustnfe/servicos/Comunicacao.py @@ -0,0 +1,54 @@ +#coding=utf-8 +''' +Created on Jun 14, 2015 + +@author: danimar +''' + +from lxml import objectify +from uuid import uuid4 +from pytrustnfe.HttpClient import HttpClient +from pytrustnfe.Certificado import converte_pfx_pem + +import logging +from logilab.common.registry import objectify_predicate +logging.basicConfig(level=logging.INFO) +logging.getLogger('suds.client').setLevel(logging.DEBUG) +logging.getLogger('suds.transport').setLevel(logging.DEBUG) + + +class Comunicacao(object): + + def __init__(self, certificado, senha): + self.certificado = certificado + self.senha = senha + + + def _preparar_temp_pem(self): + chave_temp = '/tmp/' + uuid4().hex + certificado_temp = '/tmp/' + uuid4().hex + + chave, certificado = converte_pfx_pem(self.certificado, self.senha) + arq_temp = open(chave_temp, 'w') + arq_temp.write(chave) + arq_temp.close() + + arq_temp = open(certificado_temp, 'w') + arq_temp.write(certificado) + arq_temp.close() + + return chave_temp, certificado_temp + + def envio_nfe(self): + chave, certificado = self._preparar_temp_pem() + + c = HttpClient('cad.svrs.rs.gov.br', chave, certificado) + + xml_retorno = c.post_xml('/ws/cadconsultacadastro/cadconsultacadastro2.asmx', '') + + obj = objectify.fromstring(xml_retorno) + + return xml_retorno, obj + + + \ No newline at end of file diff --git a/pytrustnfe/servicos/comunicacao.py b/pytrustnfe/servicos/comunicacao.py deleted file mode 100644 index 2683fd4..0000000 --- a/pytrustnfe/servicos/comunicacao.py +++ /dev/null @@ -1,5 +0,0 @@ -''' -Created on Jun 14, 2015 - -@author: danimar -''' diff --git a/pytrustnfe/test/XMLs/recibo_envio_1.xml b/pytrustnfe/test/XMLs/recibo_envio_1.xml new file mode 100644 index 0000000..db93561 --- /dev/null +++ b/pytrustnfe/test/XMLs/recibo_envio_1.xml @@ -0,0 +1,13 @@ + + + 2 + SVRS20140728145415 + 103 + Lote recebido com sucesso + 42 + 2014-08-18T10:32:32 + + 423002149000085 + 1 + + \ No newline at end of file diff --git a/pytrustnfe/test/XMLs/recibo_envio_2.xml b/pytrustnfe/test/XMLs/recibo_envio_2.xml new file mode 100644 index 0000000..57c6c9c --- /dev/null +++ b/pytrustnfe/test/XMLs/recibo_envio_2.xml @@ -0,0 +1,13 @@ + + + 2 + SVRS20140728145415 + 103 + Lote recebido com sucesso + 42 + 2014-08-18T17:33:28 + + 423002149008908 + 1 + + \ No newline at end of file diff --git a/pytrustnfe/test/XMLs/recibo_protocolo_sucesso_1.xml b/pytrustnfe/test/XMLs/recibo_protocolo_sucesso_1.xml new file mode 100644 index 0000000..d528f95 --- /dev/null +++ b/pytrustnfe/test/XMLs/recibo_protocolo_sucesso_1.xml @@ -0,0 +1,21 @@ + + + 2 + SVRS20140729095237 + 423002149000085 + 104 + Lote processado + 42 + + + 2 + SVRS20140729095237 + 42140803657739000169550020000000011000000018 + 2014-08-18T10:32:32 + 342140000660576 + ladI/iyYJbQx6QW5ihtlBMR1UUY= + 100 + Autorizado o uso da NF-e + + + \ No newline at end of file diff --git a/pytrustnfe/test/XMLs/recibo_protocolo_sucesso_2.xml b/pytrustnfe/test/XMLs/recibo_protocolo_sucesso_2.xml new file mode 100644 index 0000000..29a10d1 --- /dev/null +++ b/pytrustnfe/test/XMLs/recibo_protocolo_sucesso_2.xml @@ -0,0 +1,21 @@ + + + 2 + SVRS20140729095237 + 423002149008908 + 104 + Lote processado + 42 + + + 2 + SVRS20140729095237 + 42140803657739000169550020000000021000000023 + 2014-08-18T17:33:28 + 342140000666829 + oQsitGdZl1I66NNzpMn8Wf6mI7c= + 100 + Autorizado o uso da NF-e + + + \ No newline at end of file diff --git a/pytrustnfe/test/test_certificado.py b/pytrustnfe/test/test_certificado.py new file mode 100644 index 0000000..229d085 --- /dev/null +++ b/pytrustnfe/test/test_certificado.py @@ -0,0 +1,65 @@ +#coding=utf-8 +''' +Created on Jun 14, 2015 + +@author: danimar +''' +import unittest +import os, os.path +from pytrustnfe.Certificado import converte_pfx_pem + +CHAVE = '-----BEGIN PRIVATE KEY-----\n' \ + 'MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJONRp6l1y2ojgv8\n' \ + 'tP3AOLW0vjWQqiPseBLM7YAxbzz5R7LYlWHC0ZJ4uIvd4Cvc6AuoNJoeuhzFcwHx\n' \ + 'PL0TcFuW+5up1ktUohwaJ+/zKrMODCKt0gvif302yqasMnwLh9mGZQIkLkHPOX8p\n' \ + 'ZQDC4dlqwOyYDi0f+bRd5C7aWx3RAgMBAAECgYADqASP+dwTLZIXifOSNikxl4D/\n' \ + 'Is6UhU+UZ6+a9Z6kDClSrTtGaOV4k7U/AgiEDb1STKDBEPHbtKjc63Vt2gV2teem\n' \ + 'ohU0Giv+gD42uuwy2DM31OfYrpR46mzOK9JrpQc78b36ealL3AWJ1gyBbbcOWbAb\n' \ + 'KmP742V7pcD07EEp4QJBAM/e7M8VdLgOyaQzH9KHekU6fJlI4vy1UwgRUwx3/1W6\n' \ + 'zlBYo1qXfc7NSVG8ZaSrJwW4rPn393u31CpXv+oc/OMCQQC1txS6nxM9+p/641HX\n' \ + 'CHXiWJRn0Wv7rT1FyF2dHO+OQOkCCnHCsGDMf3bacTNb7iyaPbXEDac8od5uF/3h\n' \ + 'aUy7AkBDPGoAeYItXqseL2Mlp6iG5+oRcp/o+YWH4IKqT84JHslI98KutL1+vKvw\n'\ + 'gi2mW63djeR1Xh1wqP85SvTKduHdAkAIJLlIF8Lr/yRWQQO06EsoJqIX+Pmm4L+j\n'\ + 'NfSECvztWhlXHxK0D+V2pKu15GbR0t2q1+Micx4wiGyIcIjPJkHrAkAvlbXGFcGT\n'\ + 'pk9bQ8nl7EYqlvVn1TejzTLfBhBYOse/xT/NI4Kwjkan9R+EJ1cOc9EE8gm1W3jv\n'\ + 'fMw/Bh2wC5kj\n'\ + '-----END PRIVATE KEY-----\n' + +CERTIFICADO = '-----BEGIN CERTIFICATE-----\n'\ + 'MIICMTCCAZqgAwIBAgIQfYOsIEVuAJ1FwwcTrY0t1DANBgkqhkiG9w0BAQUFADBX\n'\ + 'MVUwUwYDVQQDHkwAewA1ADkARgAxAEUANAA2ADEALQBEAEQARQA1AC0ANABEADIA\n'\ + 'RgAtAEEAMAAxAEEALQA4ADMAMwAyADIAQQA5AEUAQgA4ADMAOAB9MB4XDTE1MDYx\n'\ + 'NTA1NDc1N1oXDTE2MDYxNDExNDc1N1owVzFVMFMGA1UEAx5MAHsANQA5AEYAMQBF\n'\ + 'ADQANgAxAC0ARABEAEUANQAtADQARAAyAEYALQBBADAAMQBBAC0AOAAzADMAMgAy\n'\ + 'AEEAOQBFAEIAOAAzADgAfTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAk41G\n'\ + 'nqXXLaiOC/y0/cA4tbS+NZCqI+x4EsztgDFvPPlHstiVYcLRkni4i93gK9zoC6g0\n'\ + 'mh66HMVzAfE8vRNwW5b7m6nWS1SiHBon7/Mqsw4MIq3SC+J/fTbKpqwyfAuH2YZl\n'\ + 'AiQuQc85fyllAMLh2WrA7JgOLR/5tF3kLtpbHdECAwEAATANBgkqhkiG9w0BAQUF\n'\ + 'AAOBgQArdh+RyT6VxKGsXk1zhHsgwXfToe6GpTF4W8PHI1+T0WIsNForDhvst6nm\n'\ + 'QtgAhuZM9rxpOJuNKc+pM29EixpAiZZiRMCSWEItNyEVdUIi+YnKBcAHd88TwO86\n'\ + 'd126MWQ2O8cu5W1VoDp7hYBYKOnLbYi11/StO+0rzK+oPYAvIw==\n'\ + '-----END CERTIFICATE-----\n' + +class test_assinatura(unittest.TestCase): + + caminho = os.path.dirname(__file__) + + def test_preparar_pfx(self): + dir_pfx = os.path.join(self.caminho, 'teste.pfx') + chave, certificado = converte_pfx_pem(dir_pfx, '123456') + self.assertEqual(chave, CHAVE, 'Chave gerada inválida') + self.assertEqual(certificado, CERTIFICADO, 'Certificado gerado inválido') + + def test_pfx_nao_existe(self): + self.assertRaises(Exception, converte_pfx_pem, 'file.pfx', '123456') + + def test_pfx_senha_invalida(self): + dir_pfx = os.path.join(self.caminho, 'teste.pfx') + self.assertRaises(Exception, converte_pfx_pem, dir_pfx, '123') + + def test_pfx_invalido(self): + dir_pfx = os.path.join(self.caminho, 'xml_assinado.xml') + self.assertRaises(Exception, converte_pfx_pem, dir_pfx, '123456') + + + \ No newline at end of file diff --git a/pytrustnfe/test/test_comunicacao.py b/pytrustnfe/test/test_comunicacao.py new file mode 100644 index 0000000..6c0876f --- /dev/null +++ b/pytrustnfe/test/test_comunicacao.py @@ -0,0 +1,39 @@ +#coding=utf-8 +''' +Created on Jun 16, 2015 + +@author: danimar +''' +import mock +import unittest +import os.path +from pytrustnfe.servicos.Comunicacao import Comunicacao + +XML_RETORNO = '103' \ + '42' + +class test_comunicacao(unittest.TestCase): + + caminho = os.path.dirname(__file__) + + def test_envio_nfe(self): + dir_pfx = os.path.join(self.caminho, 'teste.pfx') + + #dir_pfx = '/home/danimar/Desktop/INFOGER.pfx' #Hack + + with mock.patch('pytrustnfe.HttpClient.HTTPSConnection') as HttpsConnection: + conn = HttpsConnection.return_value + retorno = mock.MagicMock() + type(retorno).status = mock.PropertyMock(return_value='200') + retorno.read.return_value = XML_RETORNO + + conn.getresponse.return_value = retorno + + com = Comunicacao(dir_pfx, '123456') + xml, objeto = com.envio_nfe() + + self.assertEqual(xml, XML_RETORNO, 'Envio de NF-e com problemas - xml de retorno inválido') + self.assertEqual(objeto.cUF, 42, 'Envio de NF-e com problemas - objeto de retorno inválido') + self.assertEqual(objeto.cStat, 103, 'Envio de NF-e com problemas - objeto de retorno inválido') + +