From 259583461d38c9296fef48b0cdfacff11d61c504 Mon Sep 17 00:00:00 2001 From: Junior Tada Date: Tue, 8 Dec 2015 15:15:10 -0200 Subject: [PATCH] =?UTF-8?q?Implanta=C3=A7=C3=A3o=20de=20cancelarNfse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pynfe/processamento/assinatura.py | 40 +++++++++++++++++++++++++++++++++++++ pynfe/processamento/comunicacao.py | 16 +++++++++++---- pynfe/processamento/serializacao.py | 27 +++++++++++++++++++++++-- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index 10ffebb..3045c84 100644 --- a/pynfe/processamento/assinatura.py +++ b/pynfe/processamento/assinatura.py @@ -142,3 +142,43 @@ class AssinaturaA1(Assinatura): return xml except Exception as e: raise e + + def assinarCancelar(self, xml, retorna_string=False): + try: + xml = etree.fromstring(xml) + # No raiz do XML de saida + tag = 'InfPedidoCancelamento' # 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 + # TODO a proxima linha nao eh encontrada pq precisa colocar o namespace, GerarNfseEnvio. + ref = etree.SubElement(siginfo, 'Reference', URI='#' + + xml.xpath('/CancelarNfseEnvio/ns1:Pedido/ns1:InfPedidoCancelamento', namespaces={'ns1': 'http://www.betha.com.br/e-nota-contribuinte-ws'})[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('/CancelarNfseEnvio/ns1:Pedido', namespaces={'ns1': 'http://www.betha.com.br/e-nota-contribuinte-ws'})[0] + rps.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('ns1:', '').replace(':ns1', ''))) + + 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 diff --git a/pynfe/processamento/comunicacao.py b/pynfe/processamento/comunicacao.py index ac56a05..1a5d2cc 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -385,8 +385,16 @@ class ComunicacaoNfse(Comunicacao): # comunica via wsdl return self._post2(url, xml, 'consultaRps') - def cancelar(self, autorizador): - pass + def cancelar(self, autorizador, nota): + if autorizador.upper() == 'BETHA': + self._namespace = NAMESPACE_BETHA + self._versao = '2.02' + # url do serviço + url = self._get_url(autorizador) + # xml + xml = etree.tostring(nota, encoding='unicode', pretty_print=False) + # comunica via wsdl + return self._post2(url, xml, 'cancelar') def _cabecalho(self, retorna_string=True): u"""Monta o XML do cabeçalho da requisição wsdl""" @@ -394,7 +402,7 @@ class ComunicacaoNfse(Comunicacao): xml_declaration='' # cabecalho - raiz = etree.Element('cabecalho', xmlns='http://www.betha.com.br/e-nota-contribuinte-ws', versao='2.02') + raiz = etree.Element('cabecalho', xmlns=self._namespace, versao=self._versao) etree.SubElement(raiz, 'versaoDados').text = '2.02' if retorna_string: @@ -471,7 +479,7 @@ class ComunicacaoNfse(Comunicacao): elif metodo == 'consultaRps': return cliente.service.ConsultarNfsePorRps(cabecalho, xml) elif metodo == 'cancelar': - pass + return cliente.service.CancelarNfse(cabecalho, xml) # TODO outros metodos else: pass diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index 38b55ee..8ed06bd 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -696,9 +696,32 @@ class SerializacaoNfse(Serializacao): return consulta.toxml(element_name='ConsultarNfseRpsEnvio') - def cancelar(self, dados): - pass + def cancelar(self, nfse): + """Retorna string de um XML gerado a partir do + XML Schema (XSD). Binding gerado pelo modulo PyXB.""" + + # id nfse + id_nfse = nfse_schema.tcIdentificacaoNfse() + id_nfse.Numero = nfse.identificador + id_nfse.CpfCnpj = nfse.emitente.cnpj + id_nfse.InscricaoMunicipal = nfse.emitente.inscricao_municipal + id_nfse.CodigoMunicipio = nfse.emitente.endereco_cod_municipio + + # Info Pedido de cancelamento + info_pedido = nfse_schema.tcInfPedidoCancelamento() + info_pedido.Id = '1' + info_pedido.IdentificacaoNfse = id_nfse + #pedido.CodigoCancelamento = + + # Pedido + pedido = nfse_schema.tcPedidoCancelamento() + pedido.InfPedidoCancelamento = info_pedido + + # Cancelamento + cancelar = nfse_schema.CancelarNfseEnvio() + cancelar.Pedido = pedido + return cancelar.toxml(element_name='CancelarNfseEnvio') def _serializar_lote_sincrono(self, nfse): """Retorna string de um XML gerado a partir do