diff --git a/pynfe/processamento/assinatura.py b/pynfe/processamento/assinatura.py index 0fdddd7..82c3842 100644 --- a/pynfe/processamento/assinatura.py +++ b/pynfe/processamento/assinatura.py @@ -64,7 +64,7 @@ class AssinaturaA1(Assinatura): except Exception as e: raise e - def assinarNfse(self, xml, retorna_string=False): + def assinarNfse(self, xml, xpath='/GerarNfseEnvio/ns1:Rps/ns1:InfDeclaracaoPrestacaoServico', retorna_string=False): try: xml = etree.fromstring(xml) # No raiz do XML de saida @@ -76,8 +76,7 @@ class AssinaturaA1(Assinatura): # 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('/GerarNfseEnvio/ns1:Rps/ns1:InfDeclaracaoPrestacaoServico', - namespaces={'ns1': 'http://www.betha.com.br/e-nota-contribuinte-ws'})[0].attrib['Id']) + xml.xpath(xpath, 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') @@ -104,3 +103,45 @@ class AssinaturaA1(Assinatura): return xml except Exception as e: raise e + + def assinarLoteNfse(self, lote, retorna_string=False): + try: + # Assina a nota + lote = self.assinarNfse(lote, xpath='', retorna_string=True) + # Assina o lote + lote = etree.fromstring(lote) + # No raiz do XML de saida + tag = 'LoteRps' # 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 LoteRps + ref = etree.SubElement(siginfo, 'Reference', URI='#' + + lote.xpath('/EnviarLoteRpsSincronoEnvio/ns1:LoteRps', + 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') + + lote.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(lote, 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(lote, 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 2b098dc..aa53a31 100644 --- a/pynfe/processamento/comunicacao.py +++ b/pynfe/processamento/comunicacao.py @@ -368,17 +368,12 @@ class ComunicacaoNfse(Comunicacao): self._namespace = NAMESPACE_BETHA self._versao = '2.02' # url do serviço - url = self._get_url(autorizador) + NFSE[autorizador.upper()]['AUTORIZACAO'] - # gerar - raiz = etree.Element('GerarNfse') - # cabecalho - raiz.append(self._cabecalho_soap()) - dados = etree.SubElement(raiz, 'nfseDadosMsg') - dados.append(nota) - # xml soap - xml = self._construir_xml(raiz) + url = self._get_url(autorizador) + # dados + raiz = etree.Element('nfseDadosMsg') + raiz.append(nota) - return self._post(url, xml) + return self._post2(url, raiz) def consulta_nota(self, autorizador, nota): if autorizador.upper() == 'BETHA': @@ -401,13 +396,16 @@ class ComunicacaoNfse(Comunicacao): def cancelar(self, autorizador): pass - def _cabecalho_soap(self): + def _cabecalho(self, retorna_string=False): u"""Monta o XML do cabeçalho da requisição SOAP""" raiz = etree.Element('nfseCabecMsg') cabecalho = etree.SubElement(raiz, 'cabecalho', xmlns=self._namespace, versao=self._versao) etree.SubElement(cabecalho, 'versaoDados').text = self._versao - return raiz + if retorna_string: + return etree.tostring(raiz, encoding="unicode", pretty_print=False) + else: + return raiz def _construir_xml(self, dados): """Mota o XML para o envio via SOAP""" @@ -462,3 +460,17 @@ class ComunicacaoNfse(Comunicacao): raise e finally: certificadoA1.excluir() + + def _post2(self, url, xml): + # declaraçao xml + xml_declaration='' + # cabecalho + cabecalho = self._cabecalho(retorna_string=True) + # comunicacao wsdl + from suds.client import Client + cliente = Client(url) + + import ipdb + ipdb.set_trace() + + diff --git a/pynfe/processamento/serializacao.py b/pynfe/processamento/serializacao.py index a7d6ba2..36caf85 100644 --- a/pynfe/processamento/serializacao.py +++ b/pynfe/processamento/serializacao.py @@ -8,6 +8,7 @@ from pynfe.utils import etree, so_numeros, obter_municipio_por_codigo, \ from pynfe.utils.flags import CODIGOS_ESTADOS, VERSAO_PADRAO, NAMESPACE_NFE, NAMESPACE_BETHA try: from pynfe.utils import nfse_v202 as nfse_schema + from pyxb import BIND except: pass # modulo necessario apenas para NFS-e. @@ -68,6 +69,12 @@ class SerializacaoXML(Serializacao): for nf in notas_fiscais: raiz.append(self._serializar_nota_fiscal(nf, retorna_string=False)) + # Grupo de informaçoes suplementares NT2015.002 + # Somente para NFC-e + # if nf.modelo == 65: + # info = etree.Element('infNFeSupl') + # etree.SubElement(info, 'qrCode').text = '' + # raiz.append(info) if retorna_string: return etree.tostring(raiz, encoding="unicode", pretty_print=False) @@ -525,6 +532,7 @@ class SerializacaoXML(Serializacao): etree.SubElement(lacres, 'nLacre').text = lacre.numero_lacre # Somente NFC-e + """ Grupo obrigatório para a NFC-e, a critério da UF. Não informar para a NF-e (modelo 55). """ if nota_fiscal.modelo == 65: # Transporte transp = etree.SubElement(raiz, 'transp') @@ -533,10 +541,16 @@ class SerializacaoXML(Serializacao): pag = etree.SubElement(raiz, 'pag') etree.SubElement(pag, 'tPag').text = str(nota_fiscal.tipo_pagamento).zfill(2) # 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 etree.SubElement(pag, 'vPag').text = str('{:.2f}').format(nota_fiscal.totais_icms_total_nota) - #etree.SubElement(pag, 'card').text = '' - #etree.SubElement(pag, 'CNPJ').text = '' # Informar o CNPJ da Credenciadora de cartão de crédito / débito - #etree.SubElement(pag, 'tBand').text = '' # 01=Visa 02=Mastercard 03=American Express 04=Sorocred 99=Outros - #etree.SubElement(pag, 'cAut').text = '' # Identifica o número da autorização da transação da operação com cartão de crédito e/ou débito + # Cartão NT2015.002 + #cartao = etree.SubElement(pag, 'card') + """ Tipo de Integração do processo de pagamento com o sistema de automação da empresa: + 1=Pagamento integrado com o sistema de automação da empresa (Ex.: equipamento TEF, Comércio Eletrônico); + 2= Pagamento não integrado com o sistema de automação da empresa (Ex.: equipamento POS); + """ + #etree.SubElement(cartao, 'tpIntegra').text = '2' + #etree.SubElement(cartao, 'CNPJ').text = '' # Informar o CNPJ da Credenciadora de cartão de crédito / débito + #etree.SubElement(cartao, 'tBand').text = '' # 01=Visa 02=Mastercard 03=American Express 04=Sorocred 99=Outros + #etree.SubElement(cartao, 'cAut').text = '' # Identifica o número da autorização da transação da operação com cartão de crédito e/ou débito # Informações adicionais if nota_fiscal.informacoes_adicionais_interesse_fisco or nota_fiscal.informacoes_complementares_interesse_contribuinte: @@ -661,6 +675,85 @@ class SerializacaoNfse(Serializacao): return gnfse.toxml(element_name='GerarNfseEnvio') + def _serializar_lote_sincrono(self, nfse): + """Retorna string de um XML gerado a partir do + XML Schema (XSD). Binding gerado pelo modulo PyXB.""" + + servico = nfse_schema.tcDadosServico() + valores_servico = nfse_schema.tcValoresDeclaracaoServico() + valores_servico.ValorServicos = nfse.servico.valor_servico + + servico.IssRetido = nfse.servico.iss_retido + servico.ItemListaServico = nfse.servico.item_lista + servico.Discriminacao = nfse.servico.discriminacao + servico.CodigoMunicipio = nfse.servico.codigo_municipio + servico.ExigibilidadeISS = nfse.servico.exigibilidade + servico.MunicipioIncidencia = nfse.servico.municipio_incidencia + servico.Valores = valores_servico + + # Prestador + id_prestador = nfse_schema.tcIdentificacaoPrestador() + id_prestador.CpfCnpj = nfse.emitente.cnpj + id_prestador.InscricaoMunicipal = nfse.emitente.inscricao_municipal + + # Cliente + id_tomador = nfse_schema.tcIdentificacaoTomador() + id_tomador.CpfCnpj = nfse.cliente.numero_documento + if nfse.cliente.inscricao_municipal: + id_tomador.InscricaoMunicipal = nfse.cliente.inscricao_municipal + + endereco_tomador = nfse_schema.tcEndereco() + endereco_tomador.Endereco = nfse.cliente.endereco_logradouro + endereco_tomador.Numero = nfse.cliente.endereco_numero + endereco_tomador.Bairro = nfse.cliente.endereco_bairro + endereco_tomador.CodigoMunicipio = nfse.cliente.endereco_cod_municipio + endereco_tomador.Uf = nfse.cliente.endereco_uf + endereco_tomador.CodigoPais = nfse.cliente.endereco_pais + endereco_tomador.Cep = nfse.cliente.endereco_cep + + tomador = nfse_schema.tcDadosTomador() + tomador.IdentificacaoPrestador = id_tomador + tomador.RazaoSocial = nfse.cliente.razao_social + tomador.Endereco = endereco_tomador + + id_rps = nfse_schema.tcIdentificacaoRps() + id_rps.Numero = nfse.identificador + id_rps.Serie = nfse.serie + id_rps.Tipo = nfse.tipo + + rps = nfse_schema.tcInfRps() + rps.IdentificacaoRps = id_rps + rps.DataEmissao = nfse.data_emissao.strftime('%Y-%m-%d') + rps.Status = 1 + + inf_declaracao_servico = nfse_schema.tcInfDeclaracaoPrestacaoServico() + inf_declaracao_servico.Competencia = nfse.data_emissao.strftime('%Y-%m-%d') + inf_declaracao_servico.Servico = servico + inf_declaracao_servico.Prestador = id_prestador + inf_declaracao_servico.Tomador = tomador + inf_declaracao_servico.OptanteSimplesNacional = nfse.simples + inf_declaracao_servico.IncentivoFiscal = nfse.incentivo + inf_declaracao_servico.Id = nfse.identificador + inf_declaracao_servico.Rps = rps + + declaracao_servico = nfse_schema.tcDeclaracaoPrestacaoServico() + declaracao_servico.InfDeclaracaoPrestacaoServico = inf_declaracao_servico + + lote = nfse_schema.tcLoteRps() + lote.NumeroLote = 1 + lote.Id = 1 + lote.CpfCnpj = nfse.emitente.cnpj + lote.InscricaoMunicipal = nfse.emitente.inscricao_municipal + lote.QuantidadeRps = 1 + if nfse.autorizador.upper() == 'BETHA': + lote.versao = '2.02' + lote.ListaRps = BIND(declaracao_servico) + + gnfse = nfse_schema.EnviarLoteRpsSincronoEnvio() + gnfse.LoteRps = lote + + return gnfse.toxml(element_name='EnviarLoteRpsSincronoEnvio') + def _serializar_emitente(self, emitente, tag_raiz='Prestador', retorna_string=False): raiz = etree.Element(tag_raiz) documento = etree.SubElement(raiz, 'CpfCnpj') diff --git a/pynfe/utils/webservices.py b/pynfe/utils/webservices.py index 8eed5a0..88e32dc 100644 --- a/pynfe/utils/webservices.py +++ b/pynfe/utils/webservices.py @@ -435,8 +435,8 @@ NFSE = { 'CONSULTA_SERVICO_TOMADO':'ConsultarNfseServicoTomado', 'SUBSTITUIR':'SubstituirNfse', 'HTTPS':'http://e-gov.betha.com.br/e-nota-contribuinte-ws/nfseWS?wsdl', - 'HOMOLOGACAO':'http://e-gov.betha.com.br/e-nota-contribuinte-test-ws/' - #'HOMOLOGACAO':'http://e-gov.betha.com.br/e-nota-contribuinte-test-ws/nfseWS?wsdl/' + #'HOMOLOGACAO':'http://e-gov.betha.com.br/e-nota-contribuinte-test-ws/' + 'HOMOLOGACAO':'http://e-gov.betha.com.br/e-nota-contribuinte-test-ws/nfseWS?wsdl' }, # 'GINFES':{