diff --git a/README.md b/README.md index 88fbcac..72b44d6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Visão Geral Biblioteca de interface com o webservice de Nota Fiscal Eletronica, (NF-e/NFC-e/NFS-e) da SEFAZ, oficializada pelo Ministerio da Fazendo do Governo do Brasil. -Desenvolvido e testado com Python 3 no GNU/Linux. +Desenvolvido e testado com Python 3.6 no GNU/Linux. A NF-e visa substituir as notas fiscais séries 1 e 1A. A NFC-e visa substituir as notas fiscais modelo 2 e diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index a2f74a5..260d8d4 100644 --- a/pynfe/processamento/assinatura.py +++ b/pynfe/processamento/assinatura.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- - from pynfe.utils import etree, remover_acentos from pynfe.utils.flags import NAMESPACE_SIG import subprocess @@ -26,310 +25,6 @@ class Assinatura(object): class AssinaturaA1(Assinatura): - """Classe responsavel por efetuar a assinatura do certificado - digital no XML informado.""" - - def assinar(self, xml, retorna_string=False): - try: - # No raiz do XML de saida - tag = 'infNFe' # tag que será assinada - raiz = etree.Element('Signature', xmlns='http://www.w3.org/2000/09/xmldsig#') - siginfo = etree.SubElement(raiz, 'SignedInfo') - etree.SubElement(siginfo, 'CanonicalizationMethod', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(siginfo, 'SignatureMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1') - # Tenta achar a tag infNFe - try: - if len(xml.nsmap.items()) == 0: # não tem namespace - ref = etree.SubElement(siginfo, 'Reference', URI='#'+xml.findall('infNFe')[0].attrib['Id']) - else: - ns = {'ns': 'http://www.portalfiscal.inf.br/nfe'} - ref = etree.SubElement(siginfo, 'Reference', URI='#'+xml.findall('ns:infNFe', namespaces=ns)[0].attrib['Id']) - # Caso nao tenha a tag infNFe, procura a tag infEvento - except IndexError: - try: - tag = 'infEvento' - ref = etree.SubElement(siginfo, 'Reference', URI='#'+xml.findall('infEvento')[0].attrib['Id']) - # Caso nao tenha a tag infNFe, procura a tag inutNFe - except IndexError: - tag = 'infInut' - ref = etree.SubElement(siginfo, 'Reference', URI='#'+xml.findall('infInut')[0].attrib['Id']) - trans = etree.SubElement(ref, 'Transforms') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/2000/09/xmldsig#enveloped-signature') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(ref, 'DigestMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#sha1') - etree.SubElement(ref, 'DigestValue') - etree.SubElement(raiz, 'SignatureValue') - keyinfo = etree.SubElement(raiz, 'KeyInfo') - etree.SubElement(keyinfo, 'X509Data') - - xml.append(raiz) - - # Escreve no arquivo depois de remover caracteres especiais e parse string - with open('testes.xml', 'w') as arquivo: - arquivo.write(remover_acentos(etree.tostring(xml, encoding="unicode", pretty_print=False))) - - subprocess.check_call(['xmlsec1', '--sign', '--pkcs12', self.certificado, '--pwd', self.senha, '--crypto', 'openssl', '--output', 'funfa.xml', '--id-attr:Id', tag, 'testes.xml']) - xml = etree.parse('funfa.xml').getroot() - - if retorna_string: - return etree.tostring(xml, encoding="unicode", pretty_print=False) - else: - return xml - except Exception as e: - raise e - - def assinarNfse(self, xml, retorna_string=True): - "Assina NFS-e" - try: - # define variaveis de acordo com autorizador - if self.autorizador == 'ginfes': - xpath = './/ns2:InfRps' - tag = 'InfRps' - elif self.autorizador == 'betha': - xpath = './/ns1:InfDeclaracaoPrestacaoServico' - tag = 'InfDeclaracaoPrestacaoServico' - else: - raise Exception('Autorizador não encontrado!') - - xml = etree.fromstring(xml) - # define namespaces, pega do proprio xml - namespaces = xml.nsmap - # No raiz do XML de saida - raiz = etree.Element('Signature', xmlns='http://www.w3.org/2000/09/xmldsig#') - siginfo = etree.SubElement(raiz, 'SignedInfo') - etree.SubElement(siginfo, 'CanonicalizationMethod', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(siginfo, 'SignatureMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1') - # Tenta achar a tag - ref = etree.SubElement(siginfo, 'Reference', URI='#' + - xml.xpath(xpath, namespaces=namespaces)[0].attrib['Id']) - trans = etree.SubElement(ref, 'Transforms') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/2000/09/xmldsig#enveloped-signature') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(ref, 'DigestMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#sha1') - etree.SubElement(ref, 'DigestValue') - etree.SubElement(raiz, 'SignatureValue') - keyinfo = etree.SubElement(raiz, 'KeyInfo') - etree.SubElement(keyinfo, 'X509Data') - - rps = xml.xpath(xpath+'/..', namespaces=namespaces)[0] - rps.append(raiz) - - # Escreve no arquivo depois de remover caracteres especiais e parse string - with open('nfse.xml', 'w') as arquivo: - texto = remover_acentos(etree.tostring(xml, encoding="unicode", pretty_print=False)) - # se for tag do Betha - if tag == 'InfDeclaracaoPrestacaoServico': - texto = texto.replace('ns1:', '').replace(':ns1', '') - arquivo.write(texto) - - subprocess.check_call(['xmlsec1', '--sign', '--pkcs12', self.certificado, - '--pwd', self.senha, '--crypto', 'openssl', '--output', - 'nfse.xml', '--id-attr:Id', tag, 'nfse.xml']) - - if retorna_string: - return open('nfse.xml', 'r').read() - else: - return etree.parse('nfse.xml').getroot() - except Exception as e: - raise e - - def assinarLote(self, xml, retorna_string=True): - "Assina nfse e lote" - try: - xml = self.assinarNfse(xml, retorna_string=False) - xpath = './/ns1:LoteRps' - tag = 'LoteRps' - # define namespaces, pega do proprio xml - namespaces = xml.nsmap - # No raiz do XML de saida - raiz = etree.Element('Signature', xmlns='http://www.w3.org/2000/09/xmldsig#') - siginfo = etree.SubElement(raiz, 'SignedInfo') - etree.SubElement(siginfo, 'CanonicalizationMethod', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(siginfo, 'SignatureMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1') - # Tenta achar a tag - ref = etree.SubElement(siginfo, 'Reference', URI='#' + - xml.xpath(xpath, namespaces=namespaces)[0].attrib['Id']) - trans = etree.SubElement(ref, 'Transforms') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/2000/09/xmldsig#enveloped-signature') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(ref, 'DigestMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#sha1') - etree.SubElement(ref, 'DigestValue') - etree.SubElement(raiz, 'SignatureValue') - keyinfo = etree.SubElement(raiz, 'KeyInfo') - etree.SubElement(keyinfo, 'X509Data') - - # posiciona tag Signature antes do LoteRps para assinar - base = xml.xpath(xpath+'/..', namespaces=namespaces)[0] - base.insert(0, raiz) - - # Escreve no arquivo depois de remover caracteres especiais - with open('nfse.xml', 'w') as arquivo: - texto = remover_acentos(etree.tostring(xml, encoding="unicode", pretty_print=False)) - arquivo.write(texto) - # assina lote - subprocess.check_call(['xmlsec1', '--sign', '--pkcs12', self.certificado, - '--pwd', self.senha, '--crypto', 'openssl', '--output', - 'nfse.xml', '--id-attr:Id', tag, 'nfse.xml']) - - # Reposiciona tag Signature apos LoteRps - xml = etree.fromstring(open('nfse.xml', 'r').read()) - namespaces = xml.nsmap - sig = xml.find('{http://www.w3.org/2000/09/xmldsig#}Signature') - sig.getparent().remove(sig) - xml.append(sig) - - if retorna_string: - return etree.tostring(xml, encoding="unicode", pretty_print=False) - else: - return xml - except Exception as e: - raise e - - def assinarCancelar(self, xml, retorna_string=True): - """ Método que assina o xml para cancelamento de NFS-e """ - try: - if self.autorizador == 'ginfes': - xpath = 'CancelarNfseEnvio' - tag = 'CancelarNfseEnvio' - namespaces = {'ns1': 'http://www.ginfes.com.br/servico_cancelar_nfse_envio', 'ns2':'http://www.ginfes.com.br/tipos'} - elif self.autorizador == 'betha': - xpath = '/CancelarNfseEnvio/ns1:Pedido' - tag = 'InfPedidoCancelamento' - namespaces = {'ns1': 'http://www.betha.com.br/e-nota-contribuinte-ws'} - else: - raise Exception('Autorizador não encontrado!') - - xml = etree.fromstring(xml) - # No raiz do XML de saida - raiz = etree.Element('Signature', xmlns='http://www.w3.org/2000/09/xmldsig#') - siginfo = etree.SubElement(raiz, 'SignedInfo') - etree.SubElement(siginfo, 'CanonicalizationMethod', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(siginfo, 'SignatureMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1') - # Tenta achar a tag informada no xpath - if tag == 'InfPedidoCancelamento': - ref = etree.SubElement(siginfo, 'Reference', URI='#'+xml.xpath('.//ns1:'+tag, namespaces=namespaces)[0].attrib['Id']) - # ginfes não tem id no cancelamento v2 - else: - ref = etree.SubElement(siginfo, 'Reference', URI='') - trans = etree.SubElement(ref, 'Transforms') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/2000/09/xmldsig#enveloped-signature') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(ref, 'DigestMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#sha1') - etree.SubElement(ref, 'DigestValue') - etree.SubElement(raiz, 'SignatureValue') - keyinfo = etree.SubElement(raiz, 'KeyInfo') - etree.SubElement(keyinfo, 'X509Data') - - if tag == 'InfPedidoCancelamento': - xml = xml.xpath(xpath, namespaces=namespaces)[0] - # ginfes só possui a tag root - else: - xml.append(raiz) - - # Escreve no arquivo depois de remover caracteres especiais e parse string - with open('nfse.xml', 'w') as arquivo: - arquivo.write(remover_acentos(etree.tostring(xml, encoding="unicode", pretty_print=False).replace('\n',''))) - - subprocess.check_call(['xmlsec1', '--sign', '--pkcs12', self.certificado, '--pwd', self.senha, '--crypto', 'openssl', '--output', 'funfa.xml', '--id-attr:Id', tag, 'nfse.xml']) - - if retorna_string: - return open('funfa.xml', 'r').read() - else: - return etree.parse('funfa.xml').getroot() - except Exception as e: - raise e - - def assinarConsulta(self, xml, retorna_string=True): - try: - xml = etree.fromstring(xml) - # No raiz do XML de saida - tag = 'ns1:ConsultarNfseEnvio' # tag que será assinada - raiz = etree.Element('Signature', xmlns='http://www.w3.org/2000/09/xmldsig#') - siginfo = etree.SubElement(raiz, 'SignedInfo') - etree.SubElement(siginfo, 'CanonicalizationMethod', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(siginfo, 'SignatureMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1') - # Consulta nao tem id - ref = etree.SubElement(siginfo, 'Reference', URI='') - - trans = etree.SubElement(ref, 'Transforms') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/2000/09/xmldsig#enveloped-signature') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(ref, 'DigestMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#sha1') - etree.SubElement(ref, 'DigestValue') - etree.SubElement(raiz, 'SignatureValue') - keyinfo = etree.SubElement(raiz, 'KeyInfo') - etree.SubElement(keyinfo, 'X509Data') - - consulta = xml.xpath('/ns1:ConsultarNfseEnvio', namespaces={'ns1': 'http://www.ginfes.com.br/servico_consultar_nfse_envio_v03.xsd', 'ns2':'http://www.ginfes.com.br/tipos_v03.xsd'})[0] - consulta.append(raiz) - - # Escreve no arquivo depois de remover caracteres especiais e parse string - with open('nfse.xml', 'w') as arquivo: - arquivo.write(remover_acentos(etree.tostring(xml, encoding="unicode", pretty_print=False).replace('\n',''))) - - subprocess.check_call(['xmlsec1', '--sign', '--pkcs12', self.certificado, '--pwd', self.senha, '--crypto', 'openssl', '--output', 'funfa.xml', '--id-attr:Id', tag, 'nfse.xml']) - xml = etree.parse('funfa.xml').getroot() - - if retorna_string: - return etree.tostring(xml, encoding="unicode", pretty_print=False) - else: - return xml - except Exception as e: - raise e - - def assinarConsultaLote(self, xml, retorna_string=True, situacao=False): - if situacao: - tag = 'ns1:ConsultarSituacaoLoteRpsEnvio' - else: - tag = 'ns1:ConsultarLoteRpsEnvio' - return self._assinar(xml, tag, retorna_string) - - def assinarConsultaRps(self, xml, retorna_string=True): - tag = 'ns1:ConsultarNfseRpsEnvio' - return self._assinar(xml, tag, retorna_string) - - def _assinar(self, xml, tag, retorna_string=True): - """ Método para assinar xml de NFS-e com tags sem ID - Consulta de Lote e Consulta por RPS - @param tag - raiz do xml que será assinado - """ - try: - xml = etree.fromstring(xml) - # No raiz do XML de saida - raiz = etree.Element('Signature', xmlns='http://www.w3.org/2000/09/xmldsig#') - siginfo = etree.SubElement(raiz, 'SignedInfo') - etree.SubElement(siginfo, 'CanonicalizationMethod', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(siginfo, 'SignatureMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1') - # Consulta nao tem id - ref = etree.SubElement(siginfo, 'Reference', URI='') - - trans = etree.SubElement(ref, 'Transforms') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/2000/09/xmldsig#enveloped-signature') - etree.SubElement(trans, 'Transform', Algorithm='http://www.w3.org/TR/2001/REC-xml-c14n-20010315') - etree.SubElement(ref, 'DigestMethod', Algorithm='http://www.w3.org/2000/09/xmldsig#sha1') - etree.SubElement(ref, 'DigestValue') - etree.SubElement(raiz, 'SignatureValue') - keyinfo = etree.SubElement(raiz, 'KeyInfo') - etree.SubElement(keyinfo, 'X509Data') - - xml.append(raiz) - - # Escreve no arquivo depois de remover caracteres especiais e parse string - with open('nfse.xml', 'w') as arquivo: - arquivo.write(remover_acentos(etree.tostring(xml, encoding="unicode", pretty_print=False).replace('\n',''))) - - subprocess.check_call(['xmlsec1', '--sign', '--pkcs12', self.certificado, '--pwd', self.senha, '--crypto', 'openssl', '--output', 'funfa.xml', '--id-attr:Id', tag, 'nfse.xml']) - xml = etree.parse('funfa.xml').getroot() - - if retorna_string: - return etree.tostring(xml, encoding="unicode", pretty_print=False).replace('\n','') - else: - return xml - except Exception as e: - raise e - - -class AssinaturaA1SignXML(Assinatura): def __init__(self, certificado, senha): self.key, self.cert = CertificadoA1(certificado).separar_arquivo(senha) diff --git a/pynfe/processamento/comunicacao.py b/pynfe/processamento/comunicacao.py index bfbe9d1..36e061b 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -2,11 +2,7 @@ import re import ssl import datetime - import requests -from requests.adapters import HTTPAdapter -from requests.packages.urllib3.poolmanager import PoolManager - from pynfe.utils import etree, so_numeros from pynfe.utils.flags import ( NAMESPACE_NFE, @@ -20,8 +16,7 @@ from pynfe.utils.flags import ( ) from pynfe.utils.webservices import NFE, NFCE, NFSE from pynfe.entidades.certificado import CertificadoA1 - -from .assinatura import AssinaturaA1, AssinaturaA1SignXML +from .assinatura import AssinaturaA1 class Comunicacao(object): @@ -346,7 +341,7 @@ class ComunicacaoSefaz(Comunicacao): etree.SubElement(inf_inut, 'xJust').text = justificativa # assinatura - a1 = AssinaturaA1SignXML(self.certificado, self.certificado_senha) + a1 = AssinaturaA1(self.certificado, self.certificado_senha) xml = a1.assinar(raiz) # Monta XML para envio da requisição @@ -462,23 +457,6 @@ class ComunicacaoSefaz(Comunicacao): pass return self.url - def _cabecalho_soap(self, metodo): - """Monta o XML do cabeçalho da requisição SOAP""" - - raiz = etree.Element('nfeCabecMsg', xmlns=NAMESPACE_METODO+metodo) - if metodo == 'RecepcaoEvento': - etree.SubElement(raiz, 'versaoDados').text = '1.00' - elif metodo == 'NfeConsultaDest': - etree.SubElement(raiz, 'versaoDados').text = '1.01' - elif metodo == 'NfeDownloadNF': - etree.SubElement(raiz, 'versaoDados').text = '1.00' - elif metodo == 'CadConsultaCadastro2': - etree.SubElement(raiz, 'versaoDados').text = '2.00' - else: - etree.SubElement(raiz, 'versaoDados').text = VERSAO_PADRAO - etree.SubElement(raiz, 'cUF').text = CODIGOS_ESTADOS[self.uf.upper()] - return raiz - def _construir_xml_soap(self, metodo, dados): """Mota o XML para o envio via SOAP""" raiz = etree.Element('{%s}Envelope' % NAMESPACE_SOAP, nsmap={ diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index 0cca5da..877bfa8 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -3,7 +3,7 @@ from pynfe.entidades import NotaFiscal from pynfe.utils import etree, so_numeros, obter_municipio_por_codigo, \ obter_pais_por_codigo, obter_municipio_e_codigo, formatar_decimal, \ remover_acentos, obter_uf_por_codigo, obter_codigo_por_municipio -from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO, NAMESPACE_NFE, VERSAO_QRCODE +from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO, NAMESPACE_NFE, NAMESPACE_SIG, VERSAO_QRCODE from pynfe.utils.webservices import NFCE import base64 import hashlib @@ -47,6 +47,8 @@ class Serializacao(object): class SerializacaoXML(Serializacao): + """ Classe de serialização do arquivo xml """ + _versao = VERSAO_PADRAO def exportar(self, destino=None, retorna_string=False, limpar=True, **kwargs): @@ -593,8 +595,8 @@ class SerializacaoXML(Serializacao): etree.SubElement(lacres, 'nLacre').text = lacre.numero_lacre # Pagamento - """ Obrigatório o preenchimento do Grupo Informações de Pagamento para NF-e e NFC-e. Para as notas com finalidade de Ajuste ou Devolução o -campo Forma de Pagamento deve ser preenchido com 90=Sem Pagamento. """ + """ Obrigatório o preenchimento do Grupo Informações de Pagamento para NF-e e NFC-e. + Para as notas com finalidade de Ajuste ou Devolução o campo Forma de Pagamento deve ser preenchido com 90=Sem Pagamento. """ pag = etree.SubElement(raiz, 'pag') detpag = etree.SubElement(pag, 'detPag') etree.SubElement(detpag, 'tPag').text = str(nota_fiscal.tipo_pagamento).zfill(2) @@ -661,8 +663,8 @@ class SerializacaoQrcode(object): def gerar_qrcode(self, token, csc, xml, return_qr=False): """ Classe para gerar url do qrcode da NFC-e """ # Procura atributos no xml - ns = {'ns':'http://www.portalfiscal.inf.br/nfe'} - sig = {'sig':'http://www.w3.org/2000/09/xmldsig#'} + ns = {'ns':NAMESPACE_NFE} + sig = {'sig':NAMESPACE_SIG} # Tag Raiz NFe Ex: nfe = xml chave = nfe[0].attrib['Id'].replace('NFe','') @@ -698,7 +700,6 @@ class SerializacaoQrcode(object): url_hash = base64.b16encode(url_hash).decode() url = url + '&cHashQRCode=' + url_hash.upper() - # url_chave - Texto com a URL de consulta por chave de acesso a ser impressa no DANFE NFC-e. # Informar a URL da “Consulta por chave de acesso da NFC-e”. # A mesma URL que deve estar informada no DANFE NFC-e para consulta por chave de acesso