PyNFe icon indicating copy to clipboard operation
PyNFe copied to clipboard

NT 2025.001 QR_CODE VERSÃO 3

Open juniortada opened this issue 4 months ago • 2 comments

Implementa uma nova versão de QR_CODE que elimina o uso de CSC.

NT2025.001_v1.01 - NFCe_qrCode_3.pdf

juniortada avatar Sep 01 '25 03:09 juniortada

Bom dia @juniortada, como está o andamento da implementação, se puder ajudar estou a disposição.

G3nilson avatar Sep 12 '25 14:09 G3nilson

Implementei dessa forma e funcionou a emissão já no novo leiaute:

VERSAO_QRCODE_V3 = 3 class SerializacaoQrcode(object): """Gera e serializa o QR-Code da NFC-e (versão 3 – NT 2025.001)."""

def __init__(self, assinatura_provider=None):
    """
    :param assinatura_provider: callable(campos: dict) -> str
        Obrigatório em contingência (tpEmis=9). Não deve ser usado online.
    """
    self.assinatura_provider = assinatura_provider

def _get_basicos(self, nfe):
    ns = {"ns": NAMESPACE_NFE}

    chave = nfe[0].attrib["Id"].replace("NFe", "")
    tpamb = nfe.xpath("ns:infNFe/ns:ide/ns:tpAmb/text()", namespaces=ns)[0]
    cuf = nfe.xpath("ns:infNFe/ns:ide/ns:cUF/text()", namespaces=ns)[0]
    uf = [key for key, value in CODIGOS_ESTADOS.items() if value == cuf][0].upper()

    dhEmi = nfe.xpath("ns:infNFe/ns:ide/ns:dhEmi/text()", namespaces=ns)[0]
    total = nfe.xpath("ns:infNFe/ns:total/ns:ICMSTot/ns:vNF/text()", namespaces=ns)[0]
    tpEmis = nfe.xpath("ns:infNFe/ns:ide/ns:tpEmis/text()", namespaces=ns)
    tpEmis = tpEmis[0] if tpEmis else "1"

    dest_cpf = nfe.xpath("ns:infNFe/ns:dest/ns:CPF/text()", namespaces=ns)
    dest_cnpj = nfe.xpath("ns:infNFe/ns:dest/ns:CNPJ/text()", namespaces=ns)
    cpf = dest_cpf[0] if dest_cpf else None
    cnpj = dest_cnpj[0] if dest_cnpj else None

    return chave, tpamb, uf, dhEmi, total, tpEmis, cpf, cnpj

def _extrai_dia(self, dhEmi: str) -> str:
    # form. AAAA-MM-DDThh:mm:ssTZD -> captura DD
    m = re.search(r"\d{4}-\d{2}-(\d{2})", str(dhEmi))
    return m.group(1) if m else "01"

def _urls_por_uf(self, uf: str, tpamb: str):
    """
    Retorna (base_qr, url_chave) conforme seu dicionário NFCE,
    preservando o comportamento para homologação.
    """
    if tpamb == "1":  # Produção
        base_qr = NFCE[uf]["HTTPS"] + NFCE[uf]["QR"]
        url_chave = NFCE[uf]["HTTPS"] + NFCE[uf]["URL"]
    else:  # Homologação
        # PB mantém caminho base e tratamos 'hom?p=' ao montar o ?p=
        if uf == "PB":
            base_qr = NFCE[uf]["QR"]
            url_chave = NFCE[uf]["HOMOLOGACAO"]
        else:
            base_qr = NFCE[uf]["HOMOLOGACAO"] + NFCE[uf]["QR"]
            url_chave = NFCE[uf]["HOMOLOGACAO"] + NFCE[uf]["URL"]
    return base_qr.rstrip("?&"), url_chave

def gerar_qrcode(self, xml, return_qr=False):
    """
    Gera e insere o QR-Code v3 no XML da NFC-e (grupo ZX01/ZX02/ZX03).
    - Online:    ?p=<chave>|3|<tpAmb>
    - Contingência (tpEmis=9):
      ?p=<chave>|3|<tpAmb>|<dia>|<vNF>|<tp_idDest>|<idDest>|<assinatura>
    """
    nfe = xml
    chave, tpamb, uf, dhEmi, total, tpEmis, cpf, cnpj = self._get_basicos(nfe)
    base_qr, url_chave = self._urls_por_uf(uf, tpamb)

    if tpEmis == "9":
        # OFFLINE: inclui destinatário (se houver) e assinatura obrigatória
        dia = self._extrai_dia(dhEmi)

        tp_idDest, idDest = ("", "")
        if cpf:
            tp_idDest, idDest = ("1", cpf)
        elif cnpj:
            tp_idDest, idDest = ("2", cnpj)

        campos = {
            "chave": chave,
            "versao": str(VERSAO_QRCODE_V3),
            "tpAmb": tpamb,
            "dia": dia,
            "vNF": total,
            "tp_idDest": tp_idDest,
            "idDest": idDest,
        }
        if not self.assinatura_provider:
            raise ValueError("assinatura_provider é obrigatório para QR-Code v3 em contingência (tpEmis=9).")

        assinatura = self.assinatura_provider(campos=campos)
        p = f"{chave}|{VERSAO_QRCODE_V3}|{tpamb}|{dia}|{total}|{tp_idDest}|{idDest}|{assinatura}"

    else:
        # ONLINE: sem assinatura (é proibido informar)
        p = f"{chave}|{VERSAO_QRCODE_V3}|{tpamb}"

    # Montagem final da URL do QR:
    # regra geral '?p=', exceção PB/HOMO: 'hom?p='
    if uf == "PB" and tpamb != "1":
        url_qr = f"{base_qr}hom?p={p}"
    else:
        url_qr = f"{base_qr}?p={p}"

    # Atualiza <infNFeSupl>
    info = etree.Element("infNFeSupl")
    etree.SubElement(info, "qrCode").text = url_qr.strip()
    etree.SubElement(info, "urlChave").text = url_chave

    for old in nfe.xpath("infNFeSupl"):
        nfe.remove(old)
    nfe.insert(1, info)

    return (nfe, url_qr.strip()) if return_qr else nfe

chamada passando apenas o xml: xml_com_qrcode = SerializacaoQrcode().gerar_qrcode(xml)

G3nilson avatar Sep 12 '25 17:09 G3nilson