Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/_internals/apply-words-case/apply-words-case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { WordsCase } from "../number-to-words/number-to-words";

/**
* Applies a `WordsCase` to a "por extenso" string already written out in lowercase.
*
* `"sentence"` capitalizes only the first letter; `"upper"` uppercases the whole string with
* `toLocaleUpperCase("pt-BR")`, which keeps accents intact ("três" -> "TRÊS"). Any value other
* than `"sentence"` or `"upper"` (including `"lower"`, `undefined` or an invalid value) returns
* `text` unchanged, since it is already written in lowercase.
*
* @param {string} text - The lowercase "por extenso" string to transform.
* @param {WordsCase} [wordsCase] - The case to apply. Defaults to `"lower"` (no change).
* @returns {string} `text` with the requested case applied.
*
* @example
* ```typescript
* applyWordsCase("três reais"); // "três reais"
* applyWordsCase("três reais", "sentence"); // "Três reais"
* applyWordsCase("três reais", "upper"); // "TRÊS REAIS"
* ```
*/
export const applyWordsCase = (text: string, wordsCase?: WordsCase): string => {
if (wordsCase === "upper") return text.toLocaleUpperCase("pt-BR");
if (wordsCase === "sentence") return text.charAt(0).toLocaleUpperCase("pt-BR") + text.slice(1);

return text;
};
22 changes: 22 additions & 0 deletions src/_internals/constants/certidao.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Layout of the matrícula of a certidão de registro civil, 32 digits grouped as
* 6 (CNS da serventia) + 2 (acervo) + 2 (serviço) + 4 (ano) + 1 (tipo do livro) + 5 (livro) +
* 3 (folha) + 7 (termo) + 2 (dígitos verificadores).
*
* @see Official: Provimento CNJ 46/2015, art. 1º and Anexo (Código Nacional de Serventias).
* @see Based on: http://ghiorzi.org/DVnew.htm Worked example of the two check digits
* (sums 288 and 309).
* @see Based on: https://github.com/klawdyo/validation-br/blob/feat-certidao/src/certidao.ts
* Reference implementation, and the source of the matrículas used as test vectors.
* @see Based on: https://github.com/geekcom/validator-docs/blob/master/src/validator-docs/Rules/Certidao.php
* Third reference implementation agreeing on the weights and on the remainder of 10 read as 1.
*/

export const CERTIDAO_LENGTH = 32;

export const CERTIDAO_BASE_LENGTH = 30;

export const CERTIDAO_PATTERN = "000000 00 00 0000 0 00000 000 0000000 00";

export const CERTIDAO_FORMAT_REGEX =
/^\d{6}[\s.\-/]*\d{2}[\s.\-/]*\d{2}[\s.\-/]*\d{4}[\s.\-/]*\d[\s.\-/]*\d{5}[\s.\-/]*\d{3}[\s.\-/]*\d{7}[\s.\-/]*\d{2}$/;
17 changes: 17 additions & 0 deletions src/_internals/constants/cns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* CNS (Cartão Nacional de Saúde) structural constants, shared by `isValidCns` and `formatCns`.
*
* @see Official: https://rni-docs.anvisa.gov.br/docs/regras_gerais/validacoes/validacaoCNS/
*/

/** Total digits of a CNS number. */
export const CNS_LENGTH = 15;

/** Digits of the PIS/PASEP/NIS derived base embedded in a definitive CNS (starts with 1 or 2). */
export const CNS_DEFINITIVE_BASE_LENGTH = 11;

/** Suffix between the base and the check digit of a definitive CNS whose raw check digit is not 10. */
export const CNS_DEFINITIVE_SUFFIX = "000";

/** Suffix used when the raw check digit is 10: the weighted sum is raised by 2 and the digit recomputed. */
export const CNS_DEFINITIVE_ADJUSTED_SUFFIX = "001";
39 changes: 39 additions & 0 deletions src/_internals/constants/ibge-uf-codes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { StateCode } from "./states";

/**
* IBGE code of the Federative Unit ("cUF"), keyed by the 2 digit code found in the first
* field of every DF-e access key (chave de acesso): NF-e (modelo 55), NFC-e (modelo 65),
* CT-e (modelo 57) and MDF-e (modelo 58).
*
* @see Official: https://www.confaz.fazenda.gov.br/legislacao/arquivo-manuais/moc7-visao-geral.pdf
* (Manual de Orientação do Contribuinte, "chave de acesso" / "Tabela do IBGE").
*/
export const IBGE_UF_CODES: Record<string, StateCode> = {
"11": "RO",
"12": "AC",
"13": "AM",
"14": "RR",
"15": "PA",
"16": "AP",
"17": "TO",
"21": "MA",
"22": "PI",
"23": "CE",
"24": "RN",
"25": "PB",
"26": "PE",
"27": "AL",
"28": "SE",
"29": "BA",
"31": "MG",
"32": "ES",
"33": "RJ",
"35": "SP",
"41": "PR",
"42": "SC",
"43": "RS",
"50": "MS",
"51": "MT",
"52": "GO",
"53": "DF",
};
2 changes: 2 additions & 0 deletions src/_internals/constants/nfe-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Digits of a DF-e (NF-e, NFC-e, CT-e or MDF-e) access key (chave de acesso). */
export const NFE_KEY_LENGTH = 44;
122 changes: 122 additions & 0 deletions src/_internals/constants/number-words.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Portuguese (pt-BR) number-to-words tables, shared by `numberToWords` and by every public
* "por extenso" formatter (`convertNumberToWords`, `convertCurrencyToWords`, `convertDateToWords`).
*
* @see https://github.com/brazilian-utils/python/blob/main/brutils/currency.py
* "catorze" (not "quatorze") is used for 14, matching num2words pt_BR and brutils.
*/

export const ZERO_WORD = "zero";

export const UNITS: readonly string[] = [
"zero",
"um",
"dois",
"três",
"quatro",
"cinco",
"seis",
"sete",
"oito",
"nove",
"dez",
"onze",
"doze",
"treze",
"catorze",
"quinze",
"dezesseis",
"dezessete",
"dezoito",
"dezenove",
];

export const UNITS_FEMININE_OVERRIDES: Record<number, string> = {
1: "uma",
2: "duas",
};

export const TENS: readonly string[] = [
"",
"",
"vinte",
"trinta",
"quarenta",
"cinquenta",
"sessenta",
"setenta",
"oitenta",
"noventa",
];

export const HUNDRED_EXACT = "cem";

export const HUNDREDS_MASCULINE: readonly string[] = [
"",
"cento",
"duzentos",
"trezentos",
"quatrocentos",
"quinhentos",
"seiscentos",
"setecentos",
"oitocentos",
"novecentos",
];

export const HUNDREDS_FEMININE: readonly string[] = [
"",
"cento",
"duzentas",
"trezentas",
"quatrocentas",
"quinhentas",
"seiscentas",
"setecentas",
"oitocentas",
"novecentas",
];

export type NumberScaleWord = {
/** Word used for a group whose value is exactly 1 (e.g. `"mil"`, `"milhão"`). */
singular: string;
/** Word used for a group whose value is 0 or 2-999 (e.g. `"mil"`, `"milhões"`). */
plural: string;
};

export const SCALE_WORDS: readonly NumberScaleWord[] = [
{ singular: "", plural: "" },
{ singular: "mil", plural: "mil" },
{ singular: "milhão", plural: "milhões" },
{ singular: "bilhão", plural: "bilhões" },
{ singular: "trilhão", plural: "trilhões" },
];

export const MONTH_NAMES: readonly string[] = [
"janeiro",
"fevereiro",
"março",
"abril",
"maio",
"junho",
"julho",
"agosto",
"setembro",
"outubro",
"novembro",
"dezembro",
];

/**
* Portuguese (pt-BR) weekday names, indexed like `Date#getDay`/`Date#getUTCDay`
* (0 = domingo, ..., 6 = sábado), used by `convertDateToWords`'s `weekday` option.
*/
export const WEEKDAY_NAMES: readonly string[] = [
"domingo",
"segunda-feira",
"terça-feira",
"quarta-feira",
"quinta-feira",
"sexta-feira",
"sábado",
];
78 changes: 78 additions & 0 deletions src/_internals/constants/pix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* BR Code (EMV® QRCPS-MPM) field identifiers and Pix specific limits shared by the Pix
* utilities.
*
* The payload is a flat list of TLV objects: a 2 digit ID, a 2 digit length and a value of
* exactly that length. The Pix arrangement lives in one of the "Merchant Account Information"
* templates (IDs 26 to 51), the one whose GUI (sub-object `00`) is `br.gov.bcb.pix`.
*
* @see Official: https://www.bcb.gov.br/content/estabilidadefinanceira/pix/Regulamento_Pix/II_ManualdePadroesparaIniciacaodoPix.pdf
*/

export const PIX_GUI = "br.gov.bcb.pix";

export const PIX_PAYLOAD_FORMAT_INDICATOR_ID = "00";

export const PIX_PAYLOAD_FORMAT_INDICATOR = "01";

export const PIX_POINT_OF_INITIATION_ID = "01";

export const PIX_STATIC_POINT_OF_INITIATION = "11";

export const PIX_DYNAMIC_POINT_OF_INITIATION = "12";

export const PIX_MERCHANT_ACCOUNT_INFORMATION_ID = "26";

export const PIX_MERCHANT_ACCOUNT_INFORMATION_FIRST_ID = 26;

export const PIX_MERCHANT_ACCOUNT_INFORMATION_LAST_ID = 51;

export const PIX_MERCHANT_ACCOUNT_INFORMATION_MAX_LENGTH = 99;

export const PIX_GUI_ID = "00";

export const PIX_KEY_ID = "01";

export const PIX_DESCRIPTION_ID = "02";

export const PIX_URL_ID = "25";

export const PIX_MERCHANT_CATEGORY_CODE_ID = "52";

export const PIX_MERCHANT_CATEGORY_CODE = "0000";

export const PIX_TRANSACTION_CURRENCY_ID = "53";

export const PIX_TRANSACTION_CURRENCY = "986";

export const PIX_TRANSACTION_AMOUNT_ID = "54";

export const PIX_TRANSACTION_AMOUNT_MAX_LENGTH = 13;

export const PIX_COUNTRY_CODE_ID = "58";

export const PIX_COUNTRY_CODE = "BR";

export const PIX_MERCHANT_NAME_ID = "59";

export const PIX_MERCHANT_NAME_MAX_LENGTH = 25;

export const PIX_MERCHANT_CITY_ID = "60";

export const PIX_MERCHANT_CITY_MAX_LENGTH = 15;

export const PIX_ADDITIONAL_DATA_ID = "62";

export const PIX_TXID_ID = "05";

export const PIX_ABSENT_TXID = "***";

export const PIX_CRC_TAG = "6304";

export const PIX_CRC_LENGTH = 4;

export const PIX_KEY_MAX_LENGTH = 77;

export const PIX_URL_MAX_LENGTH = 77;

export const PIX_DESCRIPTION_MAX_LENGTH = 72;
46 changes: 46 additions & 0 deletions src/_internals/crc16-ccitt/crc16-ccitt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, test } from "../test/runtime";
import { crc16Ccitt } from "./crc16-ccitt";

describe("crc16Ccitt", () => {
test("should match the CRC-16/CCITT-FALSE check value", () => {
expect(crc16Ccitt("123456789")).toBe("29B1");
});

test("should return the initial value for an empty string", () => {
expect(crc16Ccitt("")).toBe("FFFF");
});

test("should always return four uppercase hexadecimal digits", () => {
for (let index = 0; index < 500; index++) {
expect(crc16Ccitt(`payload-${index}`)).toMatch(/^[0-9A-F]{4}$/);
}
});

test("should match the static QR Code example of the Bacen manual", () => {
expect(
crc16Ccitt(
"00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-4266554400005204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***6304",
),
).toBe("1D3D");
});

test("should match the dynamic QR Code example of the Bacen manual", () => {
expect(
crc16Ccitt(
"00020101021226700014br.gov.bcb.pix2548pix.example.com/8b3da2f39a4140d1a91abd93113bd4415204000053039865802BR5913Fulano de Tal6008BRASILIA62070503***6304",
),
).toBe("64E4");
});

test("should match the BR Code manual example", () => {
expect(
crc16Ccitt(
"00020104141234567890123426580014BR.GOV.BCB.PIX0136123e4567-e12b-12d1-a456-42665544000027300012BR.COM.OUTRO011001234567895204000053039865406123.455802BR5917NOME DO RECEBEDOR6008BRASILIA61087007490062190515RP12345678-201980390012BR.COM.OUTRO01190123.ABCD.3456.WXYZ6304",
),
).toBe("AD38");
});

test("should change when the payload changes", () => {
expect(crc16Ccitt("A")).not.toBe(crc16Ccitt("B"));
});
});
Loading
Loading