Browse Source

Ajustes para emissão de NFC-e 4.00

pull/34/head
Junior Tada 8 years ago
parent
commit
6d67cccb3b
  1. 2
      README.md
  2. 305
      pynfe/processamento/assinatura.py
  3. 26
      pynfe/processamento/comunicacao.py
  4. 13
      pynfe/processamento/serializacao.py

2
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

305
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)

26
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={

13
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>
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

Loading…
Cancel
Save