From 35359531e20ee6d0caffee06a7bea3e0f1540a5f Mon Sep 17 00:00:00 2001 From: Danimar Date: Sun, 7 Aug 2016 22:33:20 -0300 Subject: [PATCH] =?UTF-8?q?Removendo=20arquivos=20extras=20Assinatura=20at?= =?UTF-8?q?rav=C3=A9s=20do=20signxml=20sendo=20chamada=20corretamente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pytrustnfe/HttpClient.py | 31 ------- pytrustnfe/Servidores.py | 35 +++++--- pytrustnfe/certificado.py | 31 +++---- pytrustnfe/client.py | 23 +++++ pytrustnfe/nfe/__init__.py | 51 +++++++++++ pytrustnfe/nfe/assinatura.py | 130 ++++++++++++++++++++++++++++ pytrustnfe/nfe/comunicacao.py | 66 ++++++++++++++ pytrustnfe/servicos/NFeDistribuicaoDFe.py | 21 ----- pytrustnfe/servicos/NFeRetAutorizacao.py | 21 ----- pytrustnfe/servicos/NfeConsultaCadastro.py | 25 ------ pytrustnfe/servicos/NfeConsultaProtocolo.py | 21 ----- pytrustnfe/servicos/NfeInutilizacao.py | 21 ----- pytrustnfe/servicos/NfeStatusServico.py | 19 ---- pytrustnfe/servicos/RecepcaoEvento.py | 20 ----- pytrustnfe/servicos/Validacao.py | 15 ---- pytrustnfe/servicos/__init__.py | 0 pytrustnfe/servicos/assinatura.py | 48 ---------- pytrustnfe/servicos/comunicacao.py | 76 ---------------- pytrustnfe/servicos/nfe_autorizacao.py | 41 --------- pytrustnfe/utils.py | 33 +++++-- pytrustnfe/xml/__init__.py | 4 + pytrustnfe/xml/consultar_cadastro.xml | 0 pytrustnfe/xml/nfeEnv.xml | 18 ++-- 23 files changed, 349 insertions(+), 401 deletions(-) delete mode 100644 pytrustnfe/HttpClient.py create mode 100644 pytrustnfe/nfe/__init__.py create mode 100644 pytrustnfe/nfe/assinatura.py create mode 100644 pytrustnfe/nfe/comunicacao.py delete mode 100644 pytrustnfe/servicos/NFeDistribuicaoDFe.py delete mode 100644 pytrustnfe/servicos/NFeRetAutorizacao.py delete mode 100644 pytrustnfe/servicos/NfeConsultaCadastro.py delete mode 100644 pytrustnfe/servicos/NfeConsultaProtocolo.py delete mode 100644 pytrustnfe/servicos/NfeInutilizacao.py delete mode 100644 pytrustnfe/servicos/NfeStatusServico.py delete mode 100644 pytrustnfe/servicos/RecepcaoEvento.py delete mode 100644 pytrustnfe/servicos/Validacao.py delete mode 100644 pytrustnfe/servicos/__init__.py delete mode 100644 pytrustnfe/servicos/assinatura.py delete mode 100644 pytrustnfe/servicos/comunicacao.py delete mode 100644 pytrustnfe/servicos/nfe_autorizacao.py create mode 100644 pytrustnfe/xml/consultar_cadastro.xml diff --git a/pytrustnfe/HttpClient.py b/pytrustnfe/HttpClient.py deleted file mode 100644 index a2ee8fe..0000000 --- a/pytrustnfe/HttpClient.py +++ /dev/null @@ -1,31 +0,0 @@ -# coding=utf-8 -''' -Created on Jun 16, 2015 - -@author: danimar -''' -import requests - - -class HttpClient(object): - - def __init__(self, url, cert_path, key_path): - self.url = url - self.cert_path = cert_path - self.key_path = key_path - - def _headers(self): - return { - u'Content-type': u'application/soap+xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/NfeAutorizacao/nfeAutorizacaoLote', - u'Accept': u'application/soap+xml; charset=utf-8' - } - - def post_xml(self, post, xml): - try: - url = 'https://nfe-homologacao.sefazrs.rs.gov.br/ws/NfeAutorizacao/NFeAutorizacao.asmx' - res = requests.post(url, data=xml, cert=(self.cert_path, self.key_path), - verify=False, headers=self._headers()) - return res.text - except Exception as e: - print(str(e)) - diff --git a/pytrustnfe/Servidores.py b/pytrustnfe/Servidores.py index 5b2b763..01ba675 100644 --- a/pytrustnfe/Servidores.py +++ b/pytrustnfe/Servidores.py @@ -1,8 +1,26 @@ -''' -Created on 26/06/2015 +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +WS_NFE_AUTORIZACAO = 0 +WS_NFE_CONSULTA_AUTORIZACAO = 1 +WS_NFE_CANCELAMENTO = 2 +WS_NFE_INUTILIZACAO = 3 +WS_NFE_CONSULTA = 4 +WS_NFE_SITUACAO = 5 +WS_NFE_CONSULTA_CADASTRO = 6 + +WS_DPEC_RECEPCAO = 7 +WS_DPEC_CONSULTA = 8 + +WS_NFE_RECEPCAO_EVENTO = 9 +WS_NFE_DOWNLOAD = 10 +WS_NFE_CONSULTA_DESTINADAS = 11 +WS_DFE_DISTRIBUICAO = 12 + +NFE_AMBIENTE_PRODUCAO = 1 +NFE_AMBIENTE_HOMOLOGACAO = 2 -@author: danimar -''' def localizar_url(servico, estado): return ESTADO_WS[estado]['servidor'], ESTADO_WS[estado][servico] @@ -279,17 +297,8 @@ UFGO = { } } -#UFMA = { - #NFE_AMBIENTE_PRODUCAO: { - #'servidor': 'sistemas.sefaz.ma.gov.br', - #WS_NFE_CONSULTA_CADASTRO: 'wscadastro/CadConsultaCadastro2', - #} -#} UFMT = { -#NFeAutorizacao 3.10 https://nfe.sefaz.mt.gov.br/nfews/v2/services/NfeAutorizacao?wsdl -#NFeRetAutorizacao 3.10 https://nfe.sefaz.mt.gov.br/nfews/v2/services/NfeRetAutorizacao?wsdl - NFE_AMBIENTE_PRODUCAO: { 'servidor' : 'nfe.sefaz.mt.gov.br', WS_NFE_AUTORIZACAO : 'nfews/v2/services/NfeAutorizacao', diff --git a/pytrustnfe/certificado.py b/pytrustnfe/certificado.py index b385a03..724912d 100644 --- a/pytrustnfe/certificado.py +++ b/pytrustnfe/certificado.py @@ -1,9 +1,8 @@ -# coding=utf-8 -''' -Created on Jun 16, 2015 +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + -@author: danimar -''' from uuid import uuid4 import os.path from OpenSSL import crypto @@ -15,22 +14,18 @@ class Certificado(object): self.password = password -def converte_pfx_pem(pfx_stream, senha): - try: - certificado = crypto.load_pkcs12(pfx_stream, senha) - - cert = crypto.dump_certificate(crypto.FILETYPE_PEM, - certificado.get_certificate()) - key = crypto.dump_privatekey(crypto.FILETYPE_PEM, - certificado.get_privatekey()) - 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 +def extract_cert_and_key_from_pfx(pfx, password): + pfx = crypto.load_pkcs12(pfx, password) + # PEM formatted private key + key = crypto.dump_privatekey(crypto.FILETYPE_PEM, + pfx.get_privatekey()) + # PEM formatted certificate + cert = crypto.dump_certificate(crypto.FILETYPE_PEM, + pfx.get_certificate()) return cert, key + def save_cert_key(cert, key): cert_temp = '/tmp/' + uuid4().hex key_temp = '/tmp/' + uuid4().hex diff --git a/pytrustnfe/client.py b/pytrustnfe/client.py index 6bd130b..0867982 100644 --- a/pytrustnfe/client.py +++ b/pytrustnfe/client.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import requests import suds.client @@ -28,3 +31,23 @@ def get_client(base_url): cache=cache, transport=suds_requests.RequestsTransport(session) ) + + +class HttpClient(object): + + def __init__(self, url, cert_path, key_path): + self.url = url + self.cert_path = cert_path + self.key_path = key_path + + def _headers(self, action): + return { + u'Content-type': u'application/soap+xml; charset=utf-8; action="http://www.portalfiscal.inf.br/nfe/wsdl/%s' % action, + u'Accept': u'application/soap+xml; charset=utf-8' + } + + def post_soap(self, xml_soap, action): + res = requests.post(self.url, data=xml_soap, + cert=(self.cert_path, self.key_path), + verify=False, headers=self._headers(action)) + return res.text diff --git a/pytrustnfe/nfe/__init__.py b/pytrustnfe/nfe/__init__.py new file mode 100644 index 0000000..2a9ccf5 --- /dev/null +++ b/pytrustnfe/nfe/__init__.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +import os +from lxml import etree +from .comunicacao import Comunicacao +from .assinatura import assinar, Assinatura +from pytrustnfe import utils +from pytrustnfe.xml import render_xml + + +class NFe(Comunicacao): + + def __init__(self, cert, key): + Comunicacao.__init__(self, cert, key) + + def consultar_cadastro(self, cadastro, estado): + self.url = 'https://cad.sefazrs.rs.gov.br/ws/cadconsultacadastro/cadconsultacadastro2.asmx' + self.metodo = 'NfeConsultaCadastro' + + path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'xml') + xml = render_xml(path, 'consultar_cadastro.xml', **cadastro) + + xml_response, obj = self._executar_consulta(xml) + + return { + 'sent_xml': xml, + 'received_xml': xml_response, + 'object': obj.Body.nfeAutorizacaoLoteResult + } + + + def autorizar_nfe(self, nfe, nfe_id): + self.url = 'https://nfe-homologacao.sefazrs.rs.gov.br/ws/NfeAutorizacao/NFeAutorizacao.asmx' + self.metodo = 'NfeAutorizacao/nfeAutorizacaoLote' + + path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'xml') + xml = render_xml(path, 'nfeEnv.xml', **nfe) + + xmlElem = etree.fromstring(xml) + xml_signed = assinar(xmlElem, self.cert, self.key, '#%s' % nfe_id) + + xml_response, obj = self._executar_consulta(xml_signed) + + return { + 'sent_xml': xml_signed, + 'received_xml': xml_response, + 'object': obj.Body.nfeAutorizacaoLoteResult + } diff --git a/pytrustnfe/nfe/assinatura.py b/pytrustnfe/nfe/assinatura.py new file mode 100644 index 0000000..7754ab2 --- /dev/null +++ b/pytrustnfe/nfe/assinatura.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import xmlsec +import libxml2 +import os.path + +from signxml import XMLSigner +from signxml import methods +from lxml import etree +from OpenSSL import crypto + +NAMESPACE_SIG = 'http://www.w3.org/2000/09/xmldsig#' + + +def extract_cert_and_key_from_pfx(pfx, password): + pfx = crypto.load_pkcs12(pfx, password) + # PEM formatted private key + key = crypto.dump_privatekey(crypto.FILETYPE_PEM, + pfx.get_privatekey()) + # PEM formatted certificate + cert = crypto.dump_certificate(crypto.FILETYPE_PEM, + pfx.get_certificate()) + return cert, key + + +def recursively_empty(e): + if e.text: + return False + return all((recursively_empty(c) for c in e.iterchildren())) + + +def assinar(xml, cert, key, reference): + context = etree.iterwalk(xml) + for dummy, elem in context: + parent = elem.getparent() + if recursively_empty(elem): + parent.remove(elem) + + element = xml.find('{' + xml.nsmap[None] + '}NFe') + signer = XMLSigner(digest_algorithm=u'sha1',signature_algorithm="rsa-sha1", + method=methods.enveloped, + c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') + ns = {} + ns[None] = signer.namespaces['ds'] + signer.namespaces = ns + signed_root = signer.sign(element, key=str(key), cert=cert, reference_uri=reference) + + xml.remove(element) + xml.append(signed_root) + return etree.tostring(xml) + + +class Assinatura(object): + + def __init__(self, arquivo, senha): + self.arquivo = arquivo + self.senha = senha + + def _checar_certificado(self): + if not os.path.isfile(self.arquivo): + raise Exception('Caminho do certificado não existe.') + + def _inicializar_cripto(self): + libxml2.initParser() + libxml2.substituteEntitiesDefault(1) + + xmlsec.init() + xmlsec.cryptoAppInit(None) + xmlsec.cryptoInit() + + def _finalizar_cripto(self): + xmlsec.cryptoShutdown() + xmlsec.cryptoAppShutdown() + xmlsec.shutdown() + + libxml2.cleanupParser() + + def assina_xml(self, xml, reference): + self._checar_certificado() + self._inicializar_cripto() + try: + doc_xml = libxml2.parseMemory( + xml.encode('utf-8'), len(xml.encode('utf-8'))) + + signNode = xmlsec.TmplSignature(doc_xml, xmlsec.transformInclC14NId(), + xmlsec.transformRsaSha1Id(), None) + + doc_xml.getLastChild().addChild(signNode) + refNode = signNode.addReference(xmlsec.transformSha1Id(), + None, reference, None) + + refNode.addTransform(xmlsec.transformEnvelopedId()) + refNode.addTransform(xmlsec.transformInclC14NId()) + keyInfoNode = signNode.ensureKeyInfo() + keyInfoNode.addX509Data() + + dsig_ctx = xmlsec.DSigCtx() + chave = xmlsec.cryptoAppKeyLoad(filename=str(self.arquivo), + format=xmlsec.KeyDataFormatPkcs12, + pwd=str(self.senha), + pwdCallback=None, + pwdCallbackCtx=None) + + dsig_ctx.signKey = chave + dsig_ctx.sign(signNode) + + status = dsig_ctx.status + dsig_ctx.destroy() + + if status != xmlsec.DSigStatusSucceeded: + raise RuntimeError( + 'Erro ao realizar a assinatura do arquivo; status: "' + + str(status) + + '"') + + xpath = doc_xml.xpathNewContext() + xpath.xpathRegisterNs('sig', NAMESPACE_SIG) + certificados = xpath.xpathEval( + '//sig:X509Data/sig:X509Certificate') + for i in range(len(certificados) - 1): + certificados[i].unlinkNode() + certificados[i].freeNode() + + xml = doc_xml.serialize() + return xml + finally: + doc_xml.freeDoc() + self._finalizar_cripto() diff --git a/pytrustnfe/nfe/comunicacao.py b/pytrustnfe/nfe/comunicacao.py new file mode 100644 index 0000000..f767873 --- /dev/null +++ b/pytrustnfe/nfe/comunicacao.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + + +from lxml import objectify +from uuid import uuid4 +from pytrustnfe.HttpClient import HttpClient +from pytrustnfe.Certificado import converte_pfx_pem + +from ..xml import sanitize_response + +common_namespaces = {'soap': 'http://www.w3.org/2003/05/soap-envelope'} + +soap_body_path = './soap:Envelope/soap:Body' +soap_fault_path = './soap:Envelope/soap:Body/soap:Fault' + + +class Comunicacao(object): + url = '' + web_service = '' + metodo = '' + tag_retorno = '' + + def __init__(self, cert, key): + self.cert = cert + self.key = key + + def _soap_xml(self, body): + xml = '' + xml += '' + xml += '' + xml += '433.10' + xml += '' + xml += body + xml += '' + return xml.rstrip('\n') + + def _preparar_temp_pem(self): + cert_path = '/tmp/' + uuid4().hex + key_path = '/tmp/' + uuid4().hex + + arq_temp = open(cert_path, 'w') + arq_temp.write(self.cert) + arq_temp.close() + + arq_temp = open(key_path, 'w') + arq_temp.write(self.key) + arq_temp.close() + + return cert_path, key_path + + def _validar_dados(self): + assert self.url != '', "Url servidor não configurada" + assert self.metodo != '', "Método não configurado" + + + def _executar_consulta(self, xmlEnviar): + self._validar_dados() + cert_path, key_path = self._preparar_temp_pem() + + client = HttpClient(self.url, cert_path, key_path) + soap_xml = self._soap_xml(xmlEnviar) + xml_retorno = client.post_xml(self.web_service, soap_xml) + + return sanitize_response(xml_retorno) diff --git a/pytrustnfe/servicos/NFeDistribuicaoDFe.py b/pytrustnfe/servicos/NFeDistribuicaoDFe.py deleted file mode 100644 index 59a70cf..0000000 --- a/pytrustnfe/servicos/NFeDistribuicaoDFe.py +++ /dev/null @@ -1,21 +0,0 @@ -#coding=utf-8 -''' -Created on 21/06/2015 - -@author: danimar -''' -from pytrustnfe.servicos.Comunicacao import Comunicacao -from pytrustnfe.xml import DynamicXml - - -class NfeDistribuicaoDFe(Comunicacao): - - def distribuicao(self, dfe): - xml = self._validar_xml(recibo) - - self.metodo = 'NFeDistribuicaoDFe' - self.tag_retorno = 'retDistDFeInt' - self.web_service = 'NFeDistribuicaoDFe/NFeDistribuicaoDFe.asmx' - self.url = 'www1.nfe.fazenda.gov.br' - - return self._executar_consulta(xml) \ No newline at end of file diff --git a/pytrustnfe/servicos/NFeRetAutorizacao.py b/pytrustnfe/servicos/NFeRetAutorizacao.py deleted file mode 100644 index 82cc39e..0000000 --- a/pytrustnfe/servicos/NFeRetAutorizacao.py +++ /dev/null @@ -1,21 +0,0 @@ -#coding=utf-8 -''' -Created on 21/06/2015 - -@author: danimar -''' -from pytrustnfe.servicos.Comunicacao import Comunicacao -from pytrustnfe.xml import DynamicXml - - -class NfeRetAutorizacao(Comunicacao): - - def consulta_autorizacao(self, recibo): - xml = self._validar_xml(recibo) - - self.metodo = 'NFeRetAutorizacao' - self.tag_retorno = 'retConsReciNFe' - self.web_service = 'ws/NfeRetAutorizacao/NFeRetAutorizacao.asmx' - self.url = 'nfe.sefazrs.rs.gov.br' - - return self._executar_consulta(xml) \ No newline at end of file diff --git a/pytrustnfe/servicos/NfeConsultaCadastro.py b/pytrustnfe/servicos/NfeConsultaCadastro.py deleted file mode 100644 index e1bd79f..0000000 --- a/pytrustnfe/servicos/NfeConsultaCadastro.py +++ /dev/null @@ -1,25 +0,0 @@ -#coding=utf-8 -''' -Created on 21/06/2015 - -@author: danimar -''' -from pytrustnfe.servicos.Comunicacao import Comunicacao -from pytrustnfe.xml.DynamicXml import DynamicXml - - -class NfeConsultaCadastro(Comunicacao): - - def __init__(self, certificado, senha): - super(NfeConsultaCadastro, self).__init__(certificado, senha) - self.metodo = 'CadConsultaCadastro2' - self.tag_retorno = 'retConsCad' - - - def consultar_cadastro(self, cadastro, estado): - xml = self._validar_xml(cadastro) - - self.web_service = '/ws/cadconsultacadastro/cadconsultacadastro2.asmx' - self.url = 'cad.svrs.rs.gov.br' - - return self._executar_consulta(xml) \ No newline at end of file diff --git a/pytrustnfe/servicos/NfeConsultaProtocolo.py b/pytrustnfe/servicos/NfeConsultaProtocolo.py deleted file mode 100644 index 7ae759c..0000000 --- a/pytrustnfe/servicos/NfeConsultaProtocolo.py +++ /dev/null @@ -1,21 +0,0 @@ -#coding=utf-8 -''' -Created on 21/06/2015 - -@author: danimar -''' -from pytrustnfe.servicos.Comunicacao import Comunicacao -from pytrustnfe.xml import DynamicXml - - -class NfeConsultaProtocolo(Comunicacao): - - def consultar_protocolo(self, recibo): - xml = self._validar_xml(recibo) - - self.metodo = 'NfeConsulta2' - self.tag_retorno = 'retConsSitNFe' - self.web_service = 'ws/NfeConsulta/NfeConsulta2.asmx' - self.url = 'nfe.sefazrs.rs.gov.br' - - return self._executar_consulta(xml) \ No newline at end of file diff --git a/pytrustnfe/servicos/NfeInutilizacao.py b/pytrustnfe/servicos/NfeInutilizacao.py deleted file mode 100644 index 558ecc8..0000000 --- a/pytrustnfe/servicos/NfeInutilizacao.py +++ /dev/null @@ -1,21 +0,0 @@ -#coding=utf-8 -''' -Created on 21/06/2015 - -@author: danimar -''' -from pytrustnfe.servicos.Comunicacao import Comunicacao -from pytrustnfe.xml import DynamicXml - - -class NfeInutilizacao(Comunicacao): - - def inutilizar(self, inutilizacao): - xml = self._validar_xml(recibo) - - self.metodo = 'nfeinutilizacao2' - self.tag_retorno = 'retInutNFe' - self.web_service = 'ws/nfeinutilizacao/nfeinutilizacao2.asmx' - self.url = 'nfe.sefazrs.rs.gov.br' - - return self._executar_consulta(xml) \ No newline at end of file diff --git a/pytrustnfe/servicos/NfeStatusServico.py b/pytrustnfe/servicos/NfeStatusServico.py deleted file mode 100644 index a88839a..0000000 --- a/pytrustnfe/servicos/NfeStatusServico.py +++ /dev/null @@ -1,19 +0,0 @@ -# coding=utf-8 -''' -Created on 21/06/2015 - -@author: danimar -''' -from pytrustnfe.servicos.comunicacao import Comunicacao - -class NfeStatusServico(Comunicacao): - - def status(self, consulta): - xml = self._validar_xml(recibo) - - self.metodo = 'NfeStatusServico2' - self.tag_retorno = 'retConsStatServ' - self.web_service = 'ws/NfeStatusServico/NfeStatusServico2.asmx' - self.url = 'nfe.sefazrs.rs.gov.br' - - return self._executar_consulta(xml) diff --git a/pytrustnfe/servicos/RecepcaoEvento.py b/pytrustnfe/servicos/RecepcaoEvento.py deleted file mode 100644 index 7ea125e..0000000 --- a/pytrustnfe/servicos/RecepcaoEvento.py +++ /dev/null @@ -1,20 +0,0 @@ -#coding=utf-8 -''' -Created on 21/06/2015 - -@author: danimar -''' -from pytrustnfe.servicos.Comunicacao import Comunicacao - - -class RecepcaoEvento(Comunicacao): - - def registrar_evento(self, evento): - xml = self._validar_xml(recibo) - - self.metodo = 'RecepcaoEvento' - self.tag_retorno = 'retEnvEvento' - self.web_service = 'ws/recepcaoevento/recepcaoevento.asmx' - self.url = 'nfe.sefazrs.rs.gov.br' - - return self._executar_consulta(xml) diff --git a/pytrustnfe/servicos/Validacao.py b/pytrustnfe/servicos/Validacao.py deleted file mode 100644 index ce5ecdb..0000000 --- a/pytrustnfe/servicos/Validacao.py +++ /dev/null @@ -1,15 +0,0 @@ -''' -Created on 24/06/2015 - -@author: danimar -''' - -def validar_schema(): - arquivo_esquema = '' - xml = tira_abertura(self.xml).encode('utf-8') - - esquema = etree.XMLSchema(etree.parse(arquivo_esquema)) - esquema.validate(etree.fromstring(xml)) - - namespace = '{http://www.portalfiscal.inf.br/nfe}' - return "\n".join([x.message.replace(namespace, '') for x in esquema.error_log]) diff --git a/pytrustnfe/servicos/__init__.py b/pytrustnfe/servicos/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pytrustnfe/servicos/assinatura.py b/pytrustnfe/servicos/assinatura.py deleted file mode 100644 index 7b29e6f..0000000 --- a/pytrustnfe/servicos/assinatura.py +++ /dev/null @@ -1,48 +0,0 @@ -# coding=utf-8 -''' -Created on Jun 14, 2015 - -@author: danimar -''' - -from signxml import XMLSigner -from signxml import methods -from lxml import etree -from OpenSSL import crypto - - -def extract_cert_and_key_from_pfx(pfx, password): - pfx = crypto.load_pkcs12(pfx, password) - # PEM formatted private key - key = crypto.dump_privatekey(crypto.FILETYPE_PEM, - pfx.get_privatekey()) - # PEM formatted certificate - cert = crypto.dump_certificate(crypto.FILETYPE_PEM, - pfx.get_certificate()) - return cert, key - - -def recursively_empty(e): - if e.text: - return False - return all((recursively_empty(c) for c in e.iterchildren())) - - -def assinar(xml, cert, key, reference): - context = etree.iterwalk(xml) - for action, elem in context: - parent = elem.getparent() - if recursively_empty(elem): - parent.remove(elem) - - # element = xml.find('{' + xml.nsmap[None] + '}NFe') - signer = XMLSigner(digest_algorithm=u'sha1',signature_algorithm="rsa-sha1", - method=methods.enveloped, - c14n_algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - ns = {} - ns[None] = signer.namespaces['ds'] - signer.namespaces = ns - signed_root = signer.sign(xml, key=str(key), cert=cert, reference_uri=reference) - - # XMLSigner(signed_root, digest_algorithm=u'sha1').verify(x509_cert=cert) - return etree.tostring(signed_root) diff --git a/pytrustnfe/servicos/comunicacao.py b/pytrustnfe/servicos/comunicacao.py deleted file mode 100644 index a987214..0000000 --- a/pytrustnfe/servicos/comunicacao.py +++ /dev/null @@ -1,76 +0,0 @@ -# coding=utf-8 -''' -Created on Jun 14, 2015 - -@author: danimar -''' - -import suds.client -import suds_requests -import requests -from lxml import objectify -from uuid import uuid4 -from pytrustnfe.HttpClient import HttpClient -from pytrustnfe.Certificado import converte_pfx_pem - -from ..xml import sanitize_response - -common_namespaces = {'soap': 'http://www.w3.org/2003/05/soap-envelope'} - -soap_body_path = './soap:Envelope/soap:Body' -soap_fault_path = './soap:Envelope/soap:Body/soap:Fault' - - -class Comunicacao(object): - url = '' - web_service = '' - metodo = '' - tag_retorno = '' - - def __init__(self, cert, key): - self.cert = cert - self.key = key - - def _soap_xml(self, body): - xml = '' - xml += '' - xml += '' - xml += '433.10' - xml += '' - xml += body - xml += '' - return xml.rstrip('\n') - - def _preparar_temp_pem(self): - cert_path = '/tmp/' + uuid4().hex - key_path = '/tmp/' + uuid4().hex - - arq_temp = open(cert_path, 'w') - arq_temp.write(self.cert) - arq_temp.close() - - arq_temp = open(key_path, 'w') - arq_temp.write(self.key) - arq_temp.close() - - return cert_path, key_path - - def _validar_dados(self): - assert self.url != '', "Url servidor não configurada" - assert self.web_service != '', "Web service não especificado" - assert self.metodo != '', "Método não configurado" - - - def _validar_nfe(self, obj): - if not isinstance(obj, dict): - raise u"Objeto deve ser um dicionário de valores" - - def _executar_consulta(self, xmlEnviar): - self._validar_dados() - cert_path, key_path = self._preparar_temp_pem() - - client = HttpClient(self.url, cert_path, key_path) - soap_xml = self._soap_xml(xmlEnviar) - xml_retorno = client.post_xml(self.web_service, soap_xml) - - return sanitize_response(xml_retorno) diff --git a/pytrustnfe/servicos/nfe_autorizacao.py b/pytrustnfe/servicos/nfe_autorizacao.py deleted file mode 100644 index 78331ba..0000000 --- a/pytrustnfe/servicos/nfe_autorizacao.py +++ /dev/null @@ -1,41 +0,0 @@ -# coding=utf-8 -''' -Created on 21/06/2015 - -@author: danimar -''' -import os -from lxml import etree -from suds.sax.element import Element -from suds.sax.text import Raw -from suds.sax.parser import Parser -from pytrustnfe.servicos.comunicacao import Comunicacao -from pytrustnfe import utils -from pytrustnfe.xml import render_xml -from pytrustnfe.servicos.assinatura import assinar - - -class NfeAutorizacao(Comunicacao): - - def __init__(self, cert, key): - Comunicacao.__init__(self, cert, key) - - def autorizar_nfe(self, nfe, id): - self.url = 'nfe-homologacao.sefazrs.rs.gov.br' - self.web_service = '/ws/NfeAutorizacao/NFeAutorizacao.asmx' - self.metodo = 'nfeAutorizacaoLote' - - self._validar_nfe(nfe) - path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'xml') - xml = render_xml(path, 'nfeEnv.xml', **nfe) - - #xmlElem = etree.fromstring(xml) TODO Assinar - #xml_signed = assinar(xmlElem, self.cert, self.key, '#%s' % id) - print xml - xml_response, obj = self._executar_consulta(xml) - - return { - 'sent_xml': xml, - 'received_xml': xml_response, - 'object': obj.Body.nfeAutorizacaoLoteResult - } diff --git a/pytrustnfe/utils.py b/pytrustnfe/utils.py index 264e658..d6b5f4c 100644 --- a/pytrustnfe/utils.py +++ b/pytrustnfe/utils.py @@ -1,11 +1,32 @@ -# coding=utf-8 -''' -Created on 22/06/2015 +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + -@author: danimar -''' from datetime import date, datetime -from pytrustnfe.ChaveNFe import ChaveNFe + + +class ChaveNFe(object): + + def __init__(self, **kwargs): + self.cnpj = kwargs.pop('cnpj', '') + self.estado = kwargs.pop('estado', '') + self.emissao = kwargs.pop('emissao', '') + self.modelo = kwargs.pop('modelo', '') + self.serie = kwargs.pop('serie', '') + self.numero = kwargs.pop('numero', '') + self.tipo = kwargs.pop('tipo', '') + self.codigo = kwargs.pop('codigo', '') + + def validar(self): + assert self.cnpj != '', 'CNPJ necessário para criar chave NF-e' + assert self.estado != '', 'Estado necessário para criar chave NF-e' + assert self.emissao != '', 'Emissão necessário para criar chave NF-e' + assert self.modelo != '', 'Modelo necessário para criar chave NF-e' + assert self.serie != '', 'Série necessária para criar chave NF-e' + assert self.numero != '', 'Número necessário para criar chave NF-e' + assert self.tipo != '', 'Tipo necessário para criar chave NF-e' + assert self.codigo != '', 'Código necessário para criar chave NF-e' def date_tostring(data): diff --git a/pytrustnfe/xml/__init__.py b/pytrustnfe/xml/__init__.py index 145731f..792bc21 100644 --- a/pytrustnfe/xml/__init__.py +++ b/pytrustnfe/xml/__init__.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# © 2016 Danimar Ribeiro, Trustcode +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + import os.path import unicodedata from lxml import etree diff --git a/pytrustnfe/xml/consultar_cadastro.xml b/pytrustnfe/xml/consultar_cadastro.xml new file mode 100644 index 0000000..e69de29 diff --git a/pytrustnfe/xml/nfeEnv.xml b/pytrustnfe/xml/nfeEnv.xml index e1a21e2..2ca11ad 100644 --- a/pytrustnfe/xml/nfeEnv.xml +++ b/pytrustnfe/xml/nfeEnv.xml @@ -31,7 +31,12 @@ {% with emit = NFe.infNFe.emit %} - {{ emit.CNPJ }} + {% if emit.tipo == 'person' -%} + {{ emit.cnpj_cpf }} + {% endif %} + {% if emit.tipo == 'company' -%} + {{ emit.cnpj_cpf }} + {% endif %} {{ emit.xNome }} {{ emit.xFant }} @@ -52,8 +57,12 @@ {% with dest = NFe.infNFe.dest %} - {{ dest.CNPJ }} - {{ dest.CPF }} + {% if dest.tipo == 'person' -%} + {{ dest.cnpj_cpf }} + {% endif %} + {% if dest.tipo == 'company' -%} + {{ dest.cnpj_cpf }} + {% endif %} {{ dest.xNome }} {{ dest.enderDest.xLgr }} @@ -68,7 +77,7 @@ {{ dest.enderDest.fone }} {{ dest.indIEDest }} - {{ dest.IE }} + {% if dest.IE != '' -%}{{ dest.IE }}{% endif %} {% endwith %} {% for det in NFe.infNFe.detalhes %} @@ -169,7 +178,6 @@ {{ NFe.infNFe.infAdic.infCpl }} - {% endfor %}