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, Biblioteca de interface com o webservice de Nota Fiscal Eletronica,
(NF-e/NFC-e/NFS-e) da SEFAZ, oficializada pelo Ministerio da Fazendo do (NF-e/NFC-e/NFS-e) da SEFAZ, oficializada pelo Ministerio da Fazendo do
Governo do Brasil. 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 NF-e visa substituir as notas fiscais séries 1 e 1A.
A NFC-e visa substituir as notas fiscais modelo 2 e A NFC-e visa substituir as notas fiscais modelo 2 e

305
pynfe/processamento/assinatura.py

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from pynfe.utils import etree, remover_acentos from pynfe.utils import etree, remover_acentos
from pynfe.utils.flags import NAMESPACE_SIG from pynfe.utils.flags import NAMESPACE_SIG
import subprocess import subprocess
@ -26,310 +25,6 @@ class Assinatura(object):
class AssinaturaA1(Assinatura): 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): def __init__(self, certificado, senha):
self.key, self.cert = CertificadoA1(certificado).separar_arquivo(senha) self.key, self.cert = CertificadoA1(certificado).separar_arquivo(senha)

26
pynfe/processamento/comunicacao.py

@ -2,11 +2,7 @@
import re import re
import ssl import ssl
import datetime import datetime
import requests import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.poolmanager import PoolManager
from pynfe.utils import etree, so_numeros from pynfe.utils import etree, so_numeros
from pynfe.utils.flags import ( from pynfe.utils.flags import (
NAMESPACE_NFE, NAMESPACE_NFE,
@ -20,8 +16,7 @@ from pynfe.utils.flags import (
) )
from pynfe.utils.webservices import NFE, NFCE, NFSE from pynfe.utils.webservices import NFE, NFCE, NFSE
from pynfe.entidades.certificado import CertificadoA1 from pynfe.entidades.certificado import CertificadoA1
from .assinatura import AssinaturaA1, AssinaturaA1SignXML
from .assinatura import AssinaturaA1
class Comunicacao(object): class Comunicacao(object):
@ -346,7 +341,7 @@ class ComunicacaoSefaz(Comunicacao):
etree.SubElement(inf_inut, 'xJust').text = justificativa etree.SubElement(inf_inut, 'xJust').text = justificativa
# assinatura # assinatura
a1 = AssinaturaA1SignXML(self.certificado, self.certificado_senha)
a1 = AssinaturaA1(self.certificado, self.certificado_senha)
xml = a1.assinar(raiz) xml = a1.assinar(raiz)
# Monta XML para envio da requisição # Monta XML para envio da requisição
@ -462,23 +457,6 @@ class ComunicacaoSefaz(Comunicacao):
pass pass
return self.url 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): def _construir_xml_soap(self, metodo, dados):
"""Mota o XML para o envio via SOAP""" """Mota o XML para o envio via SOAP"""
raiz = etree.Element('{%s}Envelope' % NAMESPACE_SOAP, nsmap={ 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, \ from pynfe.utils import etree, so_numeros, obter_municipio_por_codigo, \
obter_pais_por_codigo, obter_municipio_e_codigo, formatar_decimal, \ obter_pais_por_codigo, obter_municipio_e_codigo, formatar_decimal, \
remover_acentos, obter_uf_por_codigo, obter_codigo_por_municipio 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 from pynfe.utils.webservices import NFCE
import base64 import base64
import hashlib import hashlib
@ -47,6 +47,8 @@ class Serializacao(object):
class SerializacaoXML(Serializacao): class SerializacaoXML(Serializacao):
""" Classe de serialização do arquivo xml """
_versao = VERSAO_PADRAO _versao = VERSAO_PADRAO
def exportar(self, destino=None, retorna_string=False, limpar=True, **kwargs): 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 etree.SubElement(lacres, 'nLacre').text = lacre.numero_lacre
# Pagamento # 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') pag = etree.SubElement(raiz, 'pag')
detpag = etree.SubElement(pag, 'detPag') detpag = etree.SubElement(pag, 'detPag')
etree.SubElement(detpag, 'tPag').text = str(nota_fiscal.tipo_pagamento).zfill(2) 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): def gerar_qrcode(self, token, csc, xml, return_qr=False):
""" Classe para gerar url do qrcode da NFC-e """ """ Classe para gerar url do qrcode da NFC-e """
# Procura atributos no xml # 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> # Tag Raiz NFe Ex: <NFe>
nfe = xml nfe = xml
chave = nfe[0].attrib['Id'].replace('NFe','') chave = nfe[0].attrib['Id'].replace('NFe','')
@ -698,7 +700,6 @@ class SerializacaoQrcode(object):
url_hash = base64.b16encode(url_hash).decode() url_hash = base64.b16encode(url_hash).decode()
url = url + '&cHashQRCode=' + url_hash.upper() 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. # 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”. # 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 # A mesma URL que deve estar informada no DANFE NFC-e para consulta por chave de acesso

Loading…
Cancel
Save