10 changed files with 288 additions and 5 deletions
-
24pytrustnfe/Certificado.py
-
38pytrustnfe/HttpClient.py
-
54pytrustnfe/servicos/Comunicacao.py
-
5pytrustnfe/servicos/comunicacao.py
-
13pytrustnfe/test/XMLs/recibo_envio_1.xml
-
13pytrustnfe/test/XMLs/recibo_envio_2.xml
-
21pytrustnfe/test/XMLs/recibo_protocolo_sucesso_1.xml
-
21pytrustnfe/test/XMLs/recibo_protocolo_sucesso_2.xml
-
65pytrustnfe/test/test_certificado.py
-
39pytrustnfe/test/test_comunicacao.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 |
|||
@ -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() |
|||
|
|||
|
|||
|
|||
@ -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 |
|||
|
|||
|
|||
|
|||
@ -1,5 +0,0 @@ |
|||
''' |
|||
Created on Jun 14, 2015 |
|||
|
|||
@author: danimar |
|||
''' |
|||
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<retEnviNFe versao="2.00" xmlns="http://www.portalfiscal.inf.br/nfe"> |
|||
<tpAmb>2</tpAmb> |
|||
<verAplic>SVRS20140728145415</verAplic> |
|||
<cStat>103</cStat> |
|||
<xMotivo>Lote recebido com sucesso</xMotivo> |
|||
<cUF>42</cUF> |
|||
<dhRecbto>2014-08-18T10:32:32</dhRecbto> |
|||
<infRec> |
|||
<nRec>423002149000085</nRec> |
|||
<tMed>1</tMed> |
|||
</infRec> |
|||
</retEnviNFe> |
|||
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<retEnviNFe versao="2.00" xmlns="http://www.portalfiscal.inf.br/nfe"> |
|||
<tpAmb>2</tpAmb> |
|||
<verAplic>SVRS20140728145415</verAplic> |
|||
<cStat>103</cStat> |
|||
<xMotivo>Lote recebido com sucesso</xMotivo> |
|||
<cUF>42</cUF> |
|||
<dhRecbto>2014-08-18T17:33:28</dhRecbto> |
|||
<infRec> |
|||
<nRec>423002149008908</nRec> |
|||
<tMed>1</tMed> |
|||
</infRec> |
|||
</retEnviNFe> |
|||
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<retConsReciNFe versao="2.00" xmlns="http://www.portalfiscal.inf.br/nfe"> |
|||
<tpAmb>2</tpAmb> |
|||
<verAplic>SVRS20140729095237</verAplic> |
|||
<nRec>423002149000085</nRec> |
|||
<cStat>104</cStat> |
|||
<xMotivo>Lote processado</xMotivo> |
|||
<cUF>42</cUF> |
|||
<protNFe versao="2.00"> |
|||
<infProt Id="ID342140000660576"> |
|||
<tpAmb>2</tpAmb> |
|||
<verAplic>SVRS20140729095237</verAplic> |
|||
<chNFe>42140803657739000169550020000000011000000018</chNFe> |
|||
<dhRecbto>2014-08-18T10:32:32</dhRecbto> |
|||
<nProt>342140000660576</nProt> |
|||
<digVal>ladI/iyYJbQx6QW5ihtlBMR1UUY=</digVal> |
|||
<cStat>100</cStat> |
|||
<xMotivo>Autorizado o uso da NF-e</xMotivo> |
|||
</infProt> |
|||
</protNFe> |
|||
</retConsReciNFe> |
|||
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<retConsReciNFe versao="2.00" xmlns="http://www.portalfiscal.inf.br/nfe"> |
|||
<tpAmb>2</tpAmb> |
|||
<verAplic>SVRS20140729095237</verAplic> |
|||
<nRec>423002149008908</nRec> |
|||
<cStat>104</cStat> |
|||
<xMotivo>Lote processado</xMotivo> |
|||
<cUF>42</cUF> |
|||
<protNFe versao="2.00"> |
|||
<infProt Id="ID342140000666829"> |
|||
<tpAmb>2</tpAmb> |
|||
<verAplic>SVRS20140729095237</verAplic> |
|||
<chNFe>42140803657739000169550020000000021000000023</chNFe> |
|||
<dhRecbto>2014-08-18T17:33:28</dhRecbto> |
|||
<nProt>342140000666829</nProt> |
|||
<digVal>oQsitGdZl1I66NNzpMn8Wf6mI7c=</digVal> |
|||
<cStat>100</cStat> |
|||
<xMotivo>Autorizado o uso da NF-e</xMotivo> |
|||
</infProt> |
|||
</protNFe> |
|||
</retConsReciNFe> |
|||
@ -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') |
|||
|
|||
|
|||
|
|||
@ -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 = '<retEnviNFe><cStat>103</cStat>' \ |
|||
'<cUF>42</cUF></retEnviNFe>' |
|||
|
|||
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') |
|||
|
|||
|
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue