Skip to content

Update CNPJ validation to support new alphanumeric format (from July 2026) #484

@Cloves23

Description

@Cloves23

On August 2024, the Brazilian Federal Revenue Service (Receita Federal) officially announced the adoption of alphanumeric CNPJ numbers starting July 2026. This change was published on their official website and is regulated by Normative Instruction RFB No. 2229/2024.

The new rule allows CNPJ numbers to contain uppercase letters (A-Z) in addition to digits (0-9). The check digit (DV) calculation remains numeric, but each character (letter or digit) is converted to its ASCII code and subtracted by 48, as specified in the Receita Federal's documentation.

References

Possible solution

  • Now it is accepting a alphanumeric CNPJ;
  • Only uppercase letters is allowed;
  • Using regex to validate the format and to apply or remove it;
  • Removed InvalidLength exception. This case is covered by regex;
  • Simplifying the function validate.

Note: Only functions that I changed is present. I tried to make as few changes as possible.

Target file: stdnum/br/cnpj.py

import re
from stdnum.exceptions import *

_cnpj_re = re.compile(r"^([\dA-Z]{2})\.?([\dA-Z]{3})\.?([\dA-Z]{3})/?([\dA-Z]{4})-?(\d{2})$")


def compact(number: str) -> str:
    return format(number, mask=r"\1\2\3\4\5")


def calc_check_digits(number: str) -> str:
    """Calculate the check digits for the number."""
    number = [ord(d) - 48 for d in number[:-2]]
    d1 = (11 - sum(((3 - i) % 8 + 2) * n
                   for i, n in enumerate(number[:12]))) % 11 % 10
    d2 = (11 - sum(((4 - i) % 8 + 2) * n
                   for i, n in enumerate(number[:12])) -
          2 * d1) % 11 % 10
    return "%d%d" % (d1, d2)


def validate(number: str) -> str:
    """Check if the number provided is a valid CNPJ (including alphanumeric format)."""
    number = compact(number)
    if calc_check_digits(number) != number[-2:]:
        raise InvalidChecksum()
    return number


def format(number: str, mask: str = r"\1.\2.\3/\4-\5") -> str:
    """Format the number to the presentation format: 00.000.000/0000-00"""
    match = _cnpj_re.match(number)
    if not match:
        raise InvalidFormat()
    return match.expand(mask)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions