diff --git a/pynfe/entidades/notafiscal.py b/pynfe/entidades/notafiscal.py index 62db290..8af29f7 100644 --- a/pynfe/entidades/notafiscal.py +++ b/pynfe/entidades/notafiscal.py @@ -58,8 +58,8 @@ class NotaFiscal(Entidade): # - Forma de pagamento (obrigatorio - seleciona de lista) - NF_FORMAS_PAGAMENTO forma_pagamento = int() - # - Tipo de pagamento - # 01=Dinheiro 02=Cheque 03=Cartão de Crédito 04=Cartão de Débito 05=Crédito Loja 10=Vale Alimentação 11=Vale Refeição 12=Vale Presente 13=Vale Combustível 99=Outros + # - Tipo de pagamento + # 01=Dinheiro 02=Cheque 03=Cartão de Crédito 04=Cartão de Débito 05=Crédito Loja 10=Vale Alimentação 11=Vale Refeição 12=Vale Presente 13=Vale Combustível 99=Outros tipo_pagamento = int() # - Forma de emissao (obrigatorio - seleciona de lista) - NF_FORMAS_EMISSAO @@ -398,7 +398,7 @@ class NotaFiscal(Entidade): self.dv_codigo_numerico_aleatorio = '0' return '0' self.dv_codigo_numerico_aleatorio = str(11 - remainder) - return str(self.dv_codigo_numerico_aleatorio) + return str(self.dv_codigo_numerico_aleatorio) @property # @memoize @@ -906,9 +906,7 @@ class NotaFiscalEntregaRetirada(Entidade): endereco_telefone = str() class NotaFiscalServico(Entidade): - - # Empresa que implementa o webservice - autorizador = str() # betha + # id do rps identificador = str() # tag competencia @@ -934,4 +932,3 @@ class NotaFiscalServico(Entidade): def __str__(self): return ' '.join([str(self.identificador)]) - diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index 3045c84..b3d918d 100644 --- a/pynfe/processamento/assinatura.py +++ b/pynfe/processamento/assinatura.py @@ -171,7 +171,45 @@ class AssinaturaA1(Assinatura): # 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('ns1:', '').replace(':ns1', ''))) + arquivo.write(remover_acentos(etree.tostring(xml, encoding="unicode", pretty_print=False).replace('ns1:', '').replace(':ns1', '').replace('\n',''))) + + subprocess.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 assinarConsulta(self, xml, retorna_string=False): + try: + xml = etree.fromstring(xml) + # No raiz do XML de saida + tag = '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('/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.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() diff --git a/pynfe/processamento/autorizador_nfse.py b/pynfe/processamento/autorizador_nfse.py index 48f9768..9daf84f 100644 --- a/pynfe/processamento/autorizador_nfse.py +++ b/pynfe/processamento/autorizador_nfse.py @@ -1,12 +1,12 @@ import ipdb from pyxb import BIND from importlib import import_module -import pynfe.utils.nfse.ginfes.servico_enviar_lote_rps_envio_v03 as servico_enviar_lote_rps_envio_v03 -import pynfe.utils.nfse.ginfes._tipos as _tipos +# import pynfe.utils.nfse.ginfes.servico_enviar_lote_rps_envio_v03 as servico_enviar_lote_rps_envio_v03 +# import pynfe.utils.nfse.ginfes._tipos as _tipos class InterfaceAutorizador(): #TODO Colocar raise Exception Not Implemented nos metodos - def consultar(self): + def consultar_rps(self): pass def cancelar(self): @@ -88,7 +88,7 @@ class SerializacaoBetha(InterfaceAutorizador): return gnfse.toxml(element_name='GerarNfseEnvio') - def consultar(self, nfse): + def consultar_rps(self, nfse): """Retorna string de um XML gerado a partir do XML Schema (XSD). Binding gerado pelo modulo PyXB.""" @@ -243,11 +243,13 @@ class SerializacaoGinfes(InterfaceAutorizador): def __init__(self): # importa global _tipos, servico_consultar_nfse_envio_v03 + global servico_enviar_lote_rps_envio_v03, cabecalho_v03 _tipos = import_module('pynfe.utils.nfse.ginfes._tipos') servico_consultar_nfse_envio_v03 = import_module('pynfe.utils.nfse.ginfes.servico_consultar_nfse_envio_v03') servico_enviar_lote_rps_envio_v03 = import_module('pynfe.utils.nfse.ginfes.servico_enviar_lote_rps_envio_v03') + cabecalho_v03 = import_module('pynfe.utils.nfse.ginfes.cabecalho_v03') - def consultar(self, nfse): + def consultar_rps(self, nfse): """Retorna string de um XML de consulta por Rps gerado a partir do XML Schema (XSD). Binding gerado pelo modulo PyXB.""" @@ -335,11 +337,11 @@ class SerializacaoGinfes(InterfaceAutorizador): # inf rps inf_rps = _tipos.tcInfRps() inf_rps.IdentificacaoRps = id_rps - inf_rps.DataEmissao = nfse.data_emissao.strftime('%Y-%m-%d') - inf_rps.NaturezaOperacao = 'venda' # TODO + inf_rps.DataEmissao = nfse.data_emissao.strftime('%Y-%m-%dT%H:%M:%S') + inf_rps.NaturezaOperacao = 1 # tributacao no municipio inf_rps.RegimeEspecialTributacao = None # opcional inf_rps.OptanteSimplesNacional = nfse.simples - inf_rps.IncentivadorCultural = 2 # TODO + inf_rps.IncentivadorCultural = 2 # Nao inf_rps.Status = 1 inf_rps.RpsSubstituido = None # opcional inf_rps.Servico = servico @@ -363,4 +365,11 @@ class SerializacaoGinfes(InterfaceAutorizador): enviarLote = servico_enviar_lote_rps_envio_v03.EnviarLoteRpsEnvio() enviarLote.LoteRps = lote - return enviarLote.toxml(element_name='EnviarLoteRpsEnvio') + return enviarLote.toxml("UTF-8", element_name='ns1:EnviarLoteRpsEnvio') + + def cabecalho(self): + # info + cabecalho = cabecalho_v03.cabecalho() + cabecalho.versao = '3' + cabecalho.versaoDados = '3' + return cabecalho.toxml(element_name='cabecalho') diff --git a/pynfe/processamento/comunicacao.py b/pynfe/processamento/comunicacao.py index f512a3c..50d2638 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -197,6 +197,23 @@ class ComunicacaoSefaz(Comunicacao): # Chama método que efetua a requisição POST no servidor SOAP return self._post(url, xml) + def download(self, cnpj, chave): + # url do serviço + url = self._get_url_AN(consulta='DOWNLOAD') + # Monta XML do corpo da requisição + raiz = etree.Element('downloadNFe', versao='1.00', xmlns=NAMESPACE_NFE) + etree.SubElement(raiz, 'versao').text = '1.00' + etree.SubElement(raiz, 'tpAmb').text = str(self._ambiente) + etree.SubElement(raiz, 'xServ').text = 'DOWNLOAD NFE' + etree.SubElement(raiz, 'CNPJ').text = str(cnpj) + etree.SubElement(raiz, 'chNFe').text = str(chave) + + # Monta XML para envio da requisição + xml = self._construir_xml_status_pr(cabecalho=self._cabecalho_soap(metodo='NfeDownloadNF'), metodo='NfeDownloadNF', dados=raiz) + print (url) + #return xml + return self._post(url, xml) + def inutilizar_faixa_numeracao(self, numero_inicial, numero_final, emitente, certificado, senha, ano=None, serie='1', justificativa=''): post = '/nfeweb/services/nfestatusservico.asmx' metodo = 'NfeInutilizacao2' @@ -303,6 +320,8 @@ class ComunicacaoSefaz(Comunicacao): 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' else: etree.SubElement(raiz, 'versaoDados').text = VERSAO_PADRAO etree.SubElement(raiz, 'cUF').text = CODIGOS_ESTADOS[self.uf.upper()] @@ -381,10 +400,12 @@ class ComunicacaoNfse(Comunicacao): def consulta(self, autorizador, xml): if autorizador.upper() == 'GINFES': - self._namespace = 'http://www.ginfes.com.br/servico_consultar_nfse_envio_v03.xsd' - self._versao = '3.00' + self._namespace = 'http://www.ginfes.com.br/cabecalho_v03.xsd' + self._versao = '3' # url do serviço url = self._get_url(autorizador) + # xml + xml = '' + xml # comunica via wsdl return self._post_https(url, xml, 'consulta') @@ -420,7 +441,7 @@ class ComunicacaoNfse(Comunicacao): def _cabecalho(self, retorna_string=True): u"""Monta o XML do cabeçalho da requisição wsdl""" - xml_declaration='' + xml_declaration='' # cabecalho raiz = etree.Element('cabecalho', xmlns=self._namespace, versao=self._versao) @@ -433,6 +454,11 @@ class ComunicacaoNfse(Comunicacao): else: return raiz + def _cabecalho_ginfes(self): + """ Retorna o XML do cabeçalho gerado pelo xsd""" + from pynfe.processamento.autorizador_nfse import SerializacaoGinfes + return SerializacaoGinfes().cabecalho() + def _get_url(self, autorizador): """ Retorna a url para comunicação com o webservice """ if self._ambiente == 1: @@ -469,7 +495,8 @@ class ComunicacaoNfse(Comunicacao): def _post_https(self, url, xml, metodo): # cabecalho - cabecalho = self._cabecalho() + #cabecalho = self._cabecalho() + cabecalho = self._cabecalho_ginfes() # comunicacao wsdl try: from suds.client import Client @@ -484,9 +511,11 @@ class ComunicacaoNfse(Comunicacao): if metodo == 'gerar': return cliente.service.GerarNfse(cabecalho, xml) elif metodo == 'consulta': - return cliente.service.ConsultarNfsePorRps(cabecalho, xml) + import ipdb + ipdb.set_trace() + return cliente.service.ConsultarNfseV3(cabecalho, xml) elif metodo == 'consultaRps': - return cliente.service.ConsultarNfsePorRps(cabecalho, xml) + return cliente.service.ConsultarNfsePorRpsV3(cabecalho, xml) elif metodo == 'consultaFaixa': return cliente.service.ConsultarNfseFaixa(cabecalho, xml) elif metodo == 'cancelar': diff --git a/pynfe/utils/https_nfse.py b/pynfe/utils/https_nfse.py index f5ea46d..baffcd5 100644 --- a/pynfe/utils/https_nfse.py +++ b/pynfe/utils/https_nfse.py @@ -33,6 +33,9 @@ class HttpAuthenticated(HttpTransport): self.cert = cert self.endereco = endereco - def open(self, request): - opener = urllib.request.build_opener(HTTPSClientAuthHandler(self.key, self.cert)) - return opener.open(self.endereco) \ No newline at end of file + # def open(self, request): + # opener = urllib.request.build_opener(HTTPSClientAuthHandler(self.key, self.cert)) + # return opener.open(self.endereco) + + def u2handlers(self): + return [HTTPSClientAuthHandler(self.key, self.cert)] \ No newline at end of file diff --git a/pynfe/utils/nfse/ginfes/cabecalho_v03.py b/pynfe/utils/nfse/ginfes/cabecalho_v03.py index 5639be6..2e70f8a 100644 --- a/pynfe/utils/nfse/ginfes/cabecalho_v03.py +++ b/pynfe/utils/nfse/ginfes/cabecalho_v03.py @@ -24,7 +24,7 @@ if pyxb.__version__ != _PyXBVersion: raise pyxb.PyXBVersionError(_PyXBVersion) # Import bindings for namespaces imported into schema -import _tipos as _ImportedBinding__tipos +from pynfe.utils.nfse.ginfes import _tipos as _ImportedBinding__tipos import pyxb.binding.datatypes # NOTE: All namespace declarations are reserved within the binding diff --git a/pynfe/utils/nfse/ginfes/servico_enviar_lote_rps_envio_v03.py b/pynfe/utils/nfse/ginfes/servico_enviar_lote_rps_envio_v03.py index a74af2d..ab48ff5 100644 --- a/pynfe/utils/nfse/ginfes/servico_enviar_lote_rps_envio_v03.py +++ b/pynfe/utils/nfse/ginfes/servico_enviar_lote_rps_envio_v03.py @@ -24,8 +24,8 @@ if pyxb.__version__ != _PyXBVersion: raise pyxb.PyXBVersionError(_PyXBVersion) # Import bindings for namespaces imported into schema -import _tipos as _ImportedBinding__tipos -import _dsig as _ImportedBinding__dsig +from pynfe.utils.nfse.ginfes import _tipos as _ImportedBinding__tipos +from pynfe.utils.nfse.ginfes import _dsig as _ImportedBinding__dsig import pyxb.binding.datatypes # NOTE: All namespace declarations are reserved within the binding @@ -88,18 +88,18 @@ class CTD_ANON (pyxb.binding.basis.complexTypeDefinition): _ElementMap = {} _AttributeMap = {} # Base type is pyxb.binding.datatypes.anyType - + # Element {http://www.ginfes.com.br/servico_enviar_lote_rps_envio_v03.xsd}LoteRps uses Python identifier LoteRps __LoteRps = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(Namespace, 'LoteRps'), 'LoteRps', '__httpwww_ginfes_com_brservico_enviar_lote_rps_envio_v03_xsd_CTD_ANON_httpwww_ginfes_com_brservico_enviar_lote_rps_envio_v03_xsdLoteRps', False, pyxb.utils.utility.Location('/home/leonardo/Downloads/xsd ginfes/servico_enviar_lote_rps_envio_v03.xsd', 9, 4), ) - + LoteRps = property(__LoteRps.value, __LoteRps.set, None, None) - + # Element {http://www.w3.org/2000/09/xmldsig#}Signature uses Python identifier Signature __Signature = pyxb.binding.content.ElementDeclaration(pyxb.namespace.ExpandedName(_Namespace_dsig, 'Signature'), 'Signature', '__httpwww_ginfes_com_brservico_enviar_lote_rps_envio_v03_xsd_CTD_ANON_httpwww_w3_org200009xmldsigSignature', False, pyxb.utils.utility.Location('/home/leonardo/Downloads/xsd ginfes/xmldsig-core-schema20020212_v03.xsd', 41, 0), ) - + Signature = property(__Signature.value, __Signature.set, None, None) _ElementMap.update({ @@ -107,7 +107,7 @@ class CTD_ANON (pyxb.binding.basis.complexTypeDefinition): __Signature.name() : __Signature }) _AttributeMap.update({ - + }) @@ -150,4 +150,3 @@ def _BuildAutomaton (): st_1._set_transitionSet(transitions) return fac.Automaton(states, counters, False, containing_state=None) CTD_ANON._Automaton = _BuildAutomaton() -