From b62fb2d9f542c50c9e9629de4978d6771daab658 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 19:02:37 +0000 Subject: [PATCH] fix(holidays,bank-account): add Rio de Janeiro's Corpus Christi and accept 12 digit Caixa accounts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lei RJ nº 11.002/2025 made Corpus Christi (Easter plus 60) a feriado estadual in force on its publication, and the STF upheld it in ADI 7898 (trânsito em julgado 13/08/2026), so with stateCode "RJ" it is typed "state" from 2026 on, as in the Distrito Federal and Maranhão. Goiás' 28/10/2026, moved to 30/10 by Decreto GO nº 10.987/2026, joins the one-off decree dates the table documents without carrying. The Caixa layouts (CNAB 400 notes NE051 and NE052, CNAB 240 note G011) describe the account with 12 digits, "sem operação", and publish both of its check digits with worked examples. isValidBankAccount only took the older 11 digit operação + conta form, so every 12 digit Caixa account was rejected; it now takes either digit over a 12 digit account, and keeps the compendium rule for 11 digits. The Santander account digit and the Banco do Brasil agency digit, also published in each bank's manuals, match the rules already here, and all three manuals are now cited. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01RLkm9YrtAifc6XCLFVEsdH --- docs/pt-br/utilities.md | 5 +- docs/utilities.md | 5 +- src/get-holidays/constants.ts | 18 +++++- src/get-holidays/get-holidays.test.ts | 17 ++++++ src/is-business-day/is-business-day.ts | 8 +-- .../is-valid-bank-account.test.ts | 41 +++++++++++++ .../is-valid-bank-account.ts | 57 ++++++++++++++++--- 7 files changed, 132 insertions(+), 19 deletions(-) diff --git a/docs/pt-br/utilities.md b/docs/pt-br/utilities.md index 4b1f8cda..63ba0d5e 100644 --- a/docs/pt-br/utilities.md +++ b/docs/pt-br/utilities.md @@ -1309,6 +1309,7 @@ Valida uma conta bancária brasileira. O `bankCode` precisa ser um participante - **Parâmetros** (`IsValidBankAccountParams`, todos strings): `bankCode` (3 dígitos), `agency` (1-5 dígitos), `account` (1-13 dígitos) e `digit` (1-2 caracteres, ou `X` para o Banco do Brasil e `P` para o Bradesco). - Um banco da lista é validado de uma de três formas: por uma regra de dígito verificador, apenas pela estrutura ou por um fallback genérico mod10/mod11. - Nenhum ato do Banco Central, de outro órgão de governo ou da Febraban define essas regras de dígito verificador. As dos bancos 001, 033, 041, 104, 237, 341, 399 e 745 vêm do compêndio "Regras de Validação de dígito verificador de agência e conta corrente" da Icatu Seguros, uma compilação privada da regra de cada banco. O Nubank não publica regra: o dígito de Verhoeff é o que validadores de código aberto deduziram de contas reais. +- Três bancos publicam a própria regra nos seus manuais de leiaute, e as regras daqui batem com elas: a [Caixa](https://www.caixa.gov.br/Downloads/cobranca-caixa/Manual_de_Leiaute_de_Arquivo_Eletronico_CNAB_400.pdf) os dois dígitos sobre a conta de 12 dígitos (notas NE051 e NE052), o Santander o dígito da conta ([Débito Automático 150 v08](https://www.santander.com.br/layout-de-arquivos), abril de 2026) e o [Banco do Brasil](https://www.bb.com.br/docs/pub/emp/empl/dwn/Doc5175Bloqueto.pdf) só o dígito da agência (Anexo XI); do dígito da conta ele diz só "módulo 11". - Os únicos textos oficiais sobre esses dígitos dizem que não há regra comum: o [Layout Padrão CNAB 240 v11.0](https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20padrao%20CNAB240%20V%2011_0%20-%202026_09_11.pdf) da FEBRABAN (11/09/2026), notas G009, G011 e G012, chama cada um deles de "código adotado pelo Banco" e não dá algoritmo, e a API do DICT do Banco Central recebe a conta com o dígito e não calcula nada. Bancos validados por uma regra de dígito verificador: @@ -1318,7 +1319,7 @@ Bancos validados por uma regra de dígito verificador: | Banco do Brasil | `001` | 4-5 dígitos | 8-10 dígitos | mod11 com pesos 2..9 ciclando da direita para a esquerda; `digit` pode ser `"X"` | | Santander | `033` | 4 dígitos | 8 dígitos | pesos `9,7,3,1,0,0,9,7,1,3,1,9,7,3` sobre agência + `"00"` + conta, desprezando as dezenas | | Banrisul | `041` | 4 dígitos | 9 dígitos | pesos `3,2,4,7,6,5,4,3,2`; resto 0 gera `0` e resto 1 gera `6`; `account` é tipo (2 dígitos) + conta (7 dígitos) | -| Caixa Econômica Federal | `104` | 4 dígitos | 11 dígitos | mod11 sobre agência + conta; `account` é operação (3 dígitos) + conta (8 dígitos) | +| Caixa Econômica Federal | `104` | 4 dígitos | 11-12 dígitos | mod11 com pesos 2..9 em ciclo a partir da direita, e resultado acima de 9 vira `0`. Uma `account` de 12 dígitos (o formato dos leiautes da Caixa, "sem operação") aceita tanto o dígito da conta (sobre a conta) quanto o de agência/conta (sobre agência + conta); uma de 11 dígitos é operação (3 dígitos) + conta (8 dígitos), com o dígito sobre agência + conta. Até a 2.4.0 a conta de 12 dígitos era rejeitada | | Bradesco | `237` | 4 dígitos | 7 dígitos | mod11 com pesos 2..7 ciclando da direita para a esquerda; resto 0 gera `0` e resto 1 gera `"P"` | | Nubank | `260` | 4 dígitos | 5-13 dígitos | dígito de Verhoeff sobre a conta, ignorando zeros à esquerda (sem regra publicada; veja acima) | | Itaú Unibanco | `341` | 4 dígitos | 5 dígitos | mod10 sobre agência + conta | @@ -1968,7 +1969,7 @@ Retorna os feriados brasileiros de um ano: os nacionais e, com um `stateCode`, t - O primeiro turno das eleições, "Eleições (primeiro turno)", é feriado nacional nos anos pares a partir de 1998 (Código Eleitoral, art. 380): o primeiro domingo de outubro, ou 15/11 em 2020 (EC nº 107/2020). O segundo turno fica de fora, porque só acontece onde é necessário. Por cair num domingo, nunca muda uma contagem de dias úteis. - Cada feriado nacional de data fixa só é listado nos anos em que uma norma federal o declarava (a Sexta-feira Santa, que as portarias do calendário federal listam como feriado nacional todo ano, é listada em todos os anos): Nossa Senhora Aparecida a partir de 1980, Natal a partir de 1922, Dia do trabalhador a partir de 1925, Tiradentes até 1930, de 1933 a 1948 e a partir de 1951, e Finados até 1948 e a partir de 2003. A Lei nº 662/1949 deixou Finados fora dos feriados nacionais que o Decreto-lei nº 486/1938 listava, e só a Lei nº 10.607/2002 o recolocou (o parecer da Câmara sobre o projeto: "Só inova ao sugerir o dia de finados"); até a 2.4.0 ele era listado em todos os anos. - As entradas `"optional"` são os pontos facultativos de dia inteiro do calendário federal (Portarias MGI nº 8.617/2023, 9.783/2024 e 11.460/2025, de 2024 a 2026, que listam as duas datas de Carnaval como ponto facultativo, nunca como feriado nacional): a segunda e a terça-feira de Carnaval e o Corpus Christi, os mesmos três dias que o mercado financeiro não conta como úteis (Resolução CMN nº 4.880/2020), mais os estaduais que uma norma estadual declara (o 16/09 de AL, de 2020 a 2023, o 08/12 do AM, que o estado declara para as suas repartições por decreto, e o 06/03 de PE em 2008 e 2009). O `includeOptional` liga e desliga exatamente esses. Os parciais ficam de fora: a Quarta-feira de Cinzas (até as 14h), 28/10 (Dia do Servidor Público) e as tardes de 24/12 e 31/12. -- As regras por estado (o deslocamento para domingo em SC da data que cai de segunda a sexta, ficando na data a que cai no sábado, já que o "dia útil da semana" da lei não resolve o sábado; o 30/11 de AL antecipado para segunda quando cai na terça e adiado para sexta quando cai na quinta, o Corpus Christi no DF e, desde 2024, no MA, e a terça-feira de Carnaval no RJ com tipo `"state"`, datas que deixaram de ser feriado) seguem a lei de cada estado; veja a fonte para a lista. +- As regras por estado (o deslocamento para domingo em SC da data que cai de segunda a sexta, ficando na data a que cai no sábado, já que o "dia útil da semana" da lei não resolve o sábado; o 30/11 de AL antecipado para segunda quando cai na terça e adiado para sexta quando cai na quinta, o Corpus Christi no DF, no MA (desde 2024) e no RJ (desde 2026), e a terça-feira de Carnaval no RJ com tipo `"state"`, datas que deixaram de ser feriado) seguem a lei de cada estado; veja a fonte para a lista. - Outros deslocamentos não são aplicados e a data da lei é a retornada: a lei do AC adia para a sexta-feira os feriados que caem de terça a quinta (Lei AC nº 2.126/2009), mas os próprios decretos anuais do estado a aplicam de forma desigual (em 2026 o 20/1 é adiado e o 17/11, uma terça, fica na data). - As três datas de GO (26/7, 24/10, 28/10) são os "feriados estaduais" do estatuto dos servidores do estado (Lei GO nº 20.756/2020, art. 269, II); não foi achada lei goiana que fixe uma data magna como feriado civil. O governador transfere o 26/7 por decreto todo ano (2025: 28/7; 2026: 20/7), então o 26/7 da lei, que é o retornado aqui, em geral não é o dia observado. - Cada feriado estadual só é listado a partir do primeiro ano em que a sua lei estadual se aplicava (o 9 de julho de SP a partir de 1997, o São Jorge do RJ a partir de 2008, o 11 de agosto de SC a partir de 2004), então um ano mais antigo tem menos feriados estaduais. diff --git a/docs/utilities.md b/docs/utilities.md index 1bfde00d..205d1f18 100644 --- a/docs/utilities.md +++ b/docs/utilities.md @@ -1309,6 +1309,7 @@ Check if a Brazilian bank account is valid. The `bankCode` must be a Banco Centr - **Params** (`IsValidBankAccountParams`, all strings): `bankCode` (3 digits), `agency` (1-5 digits), `account` (1-13 digits) and `digit` (1-2 characters, or `X` for Banco do Brasil and `P` for Bradesco). - A listed bank is validated in one of three ways: by a check digit rule, by structure only, or by a generic mod10/mod11 fallback. - No act of the Banco Central, of another government body or of Febraban sets these check digit rules. Those of banks 001, 033, 041, 104, 237, 341, 399 and 745 come from the "Regras de Validação de dígito verificador de agência e conta corrente" compendium of Icatu Seguros, a private compilation of each bank's rule. Nubank publishes no rule: its Verhoeff digit is the one open source validators derived from real accounts. +- Three banks publish their own rule in their layout manuals, and the rules here match them: the [Caixa](https://www.caixa.gov.br/Downloads/cobranca-caixa/Manual_de_Leiaute_de_Arquivo_Eletronico_CNAB_400.pdf) both of its digits over a 12 digit account (notes NE051 and NE052), Santander the account digit ([Débito Automático 150 v08](https://www.santander.com.br/layout-de-arquivos), April 2026), and [Banco do Brasil](https://www.bb.com.br/docs/pub/emp/empl/dwn/Doc5175Bloqueto.pdf) only the agency digit (Anexo XI), its account digit being only "módulo 11". - The only official texts on these digits say there is no common rule: the FEBRABAN [Layout Padrão CNAB 240 v11.0](https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20padrao%20CNAB240%20V%2011_0%20-%202026_09_11.pdf) (11/09/2026), notes G009, G011 and G012, calls each of them a "código adotado pelo Banco" and gives no algorithm, and the DICT API of the Banco Central takes the account with its digit and computes nothing. Banks validated by a check digit rule: @@ -1318,7 +1319,7 @@ Banks validated by a check digit rule: | Banco do Brasil | `001` | 4-5 digits | 8-10 digits | mod11 with weights 2..9 cycling from the right; `digit` may be `"X"` | | Santander | `033` | 4 digits | 8 digits | weights `9,7,3,1,0,0,9,7,1,3,1,9,7,3` over agency + `"00"` + account, tens discarded | | Banrisul | `041` | 4 digits | 9 digits | weights `3,2,4,7,6,5,4,3,2`; remainder 0 gives `0` and remainder 1 gives `6`; `account` is tipo (2 digits) + conta (7 digits) | -| Caixa Econômica Federal | `104` | 4 digits | 11 digits | mod11 over agency + account; `account` is operação (3 digits) + conta (8 digits) | +| Caixa Econômica Federal | `104` | 4 digits | 11-12 digits | mod11 with weights 2..9 cycling from the right, a result above 9 giving `0`. A 12 digit `account` (the format of the Caixa layouts, "sem operação") takes either its own digit (over the account) or the agency/account one (over agency + account); an 11 digit one is operação (3 digits) + conta (8 digits), with the digit over agency + account. Up to 2.4.0 a 12 digit account was rejected | | Bradesco | `237` | 4 digits | 7 digits | mod11 with weights 2..7 cycling from the right; remainder 0 gives `0` and remainder 1 gives `"P"` | | Nubank | `260` | 4 digits | 5-13 digits | Verhoeff check digit over the account, leading zeros dropped (no published rule; see above) | | Itaú Unibanco | `341` | 4 digits | 5 digits | mod10 over agency + account | @@ -1968,7 +1969,7 @@ Get the Brazilian holidays of a year: the national ones and, with a `stateCode`, - The first round of the elections, "Eleições (primeiro turno)", is a national holiday in the even years from 1998 on (Código Eleitoral, art. 380): the first Sunday of October, or Nov 15 in 2020 (EC nº 107/2020). The second round is left out, since it is held only where one is needed. Being a Sunday, it never changes a business day count. - Each fixed-date national holiday is listed only for the years a federal norm declared it (Sexta-feira Santa, which the federal calendar portarias list as a feriado nacional every year, is listed every year): Nossa Senhora Aparecida from 1980, Natal from 1922, Dia do trabalhador from 1925, Tiradentes up to 1930, from 1933 to 1948 and from 1951, and Finados up to 1948 and from 2003. Lei nº 662/1949 left Finados out of the feriados nacionais that Decreto-lei nº 486/1938 listed, and only Lei nº 10.607/2002 put it back (the Câmara report on its bill: "Só inova ao sugerir o dia de finados"); up to 2.4.0 it was listed every year. - The `"optional"` entries are the whole-day pontos facultativos of the federal calendar (Portarias MGI nº 8.617/2023, 9.783/2024 and 11.460/2025, for 2024 to 2026, all of which list both Carnaval days as ponto facultativo, never as feriado nacional): Carnaval Monday and Tuesday and Corpus Christi, the same three days the financial market skips (Resolução CMN nº 4.880/2020), plus the state ones a state norm declares (AL's Sep 16 from 2020 to 2023, AM's Dec 8, which the state declares for its offices by decree, and PE's Mar 6 in 2008 and 2009). `includeOptional` switches exactly these. The partial ones are left out: Quarta-feira de Cinzas (until 14h), Oct 28 (Dia do Servidor Público) and the Dec 24 and Dec 31 afternoons. -- Per-state rules (SC's Sunday shift of a date falling Monday to Friday, a Saturday date staying, since the law's "dia útil da semana" does not settle Saturday; AL's Nov 30 moved to Monday from a Tuesday and to Friday from a Thursday, DF's and, from 2024, MA's Corpus Christi and RJ's Carnaval Tuesday typed `"state"`, dates that stopped being holidays) follow each state's law; see the source for the list. +- Per-state rules (SC's Sunday shift of a date falling Monday to Friday, a Saturday date staying, since the law's "dia útil da semana" does not settle Saturday; AL's Nov 30 moved to Monday from a Tuesday and to Friday from a Thursday, DF's, MA's (from 2024) and RJ's (from 2026) Corpus Christi and RJ's Carnaval Tuesday typed `"state"`, dates that stopped being holidays) follow each state's law; see the source for the list. - Other shifts are not applied and the statutory date is returned: AC's law moves the feriados falling Tuesday to Thursday to the Friday (Lei AC nº 2.126/2009), but the state's own yearly decrees apply it unevenly (2026 moves Jan 20 and leaves Nov 17, a Tuesday, in place). - GO's three dates (Jul 26, Oct 24, Oct 28) are the "feriados estaduais" of the state servants' statute (Lei GO nº 20.756/2020, art. 269, II); no Goiás law fixing a data magna as a civil holiday was found. The governor moves Jul 26 by decree every year (2025: Jul 28; 2026: Jul 20), so the statutory Jul 26 returned here is usually not the day observed. - Each state holiday is listed only from the first year its state law applied (SP's Jul 9 from 1997, RJ's São Jorge from 2008, SC's Aug 11 from 2004), so an older year has fewer state holidays. diff --git a/src/get-holidays/constants.ts b/src/get-holidays/constants.ts index 4faca64e..84fe4526 100644 --- a/src/get-holidays/constants.ts +++ b/src/get-holidays/constants.ts @@ -234,8 +234,9 @@ const SC_NEXT_SUNDAY_TRANSFER_SINCE_YEAR = 2005; * below), a date no rule derives from the year; São Paulo moved 09/07 to * 25/05 for 2020 alone (Lei SP nº 17.264/2020), a one-off this table does not carry, and so are * Piauí's 19/10 of 2020 and 2021, brought forward to 15/05/2020 and 18/03/2021 by Leis PI nº - * 7.371/2020 and 7.490/2021, and Tocantins' 05/10/2026, which the executive moved to 09/10 for its - * own offices under Lei TO nº 1.088/1999 (both cited below). + * 7.371/2020 and 7.490/2021, Tocantins' 05/10/2026, which the executive moved to 09/10 for its + * own offices under Lei TO nº 1.088/1999 (both cited below), and Goiás' 28/10/2026, moved to 30/10 + * by Decreto GO nº 10.987, de 10/09/2026, under the same § 1º. * * Every entry starts (`since`) in the first year the norm cited for it was in force on the date, * and one that lost its basis stops (`until`, exclusive) in the first year it no longer applied, so @@ -510,6 +511,18 @@ const SC_NEXT_SUNDAY_TRANSFER_SINCE_YEAR = 2005; * STF ADI 4131, cited here before as pending against Lei RJ nº 4.007/2002, in fact sought "a * declaração de inconstitucionalidade da Lei n. 5.243, do Estado do Rio de Janeiro, de 14 de maio * de 2008" and was não conhecida on 21/09/2018 (trânsito em julgado 25/10/2018). + * @see Official: https://alerjln1.alerj.rj.gov.br/contlei.nsf/f25edae7e64db53b032564fe005262ef/46837e4d22b01f3503258d2c0048abda?OpenDocument + * Lei RJ nº 11.002, de 22/10/2025 (DO nº 196, 23/10/2025), art. 1º: "Fica instituído, no âmbito + * do Estado do Rio de Janeiro, o Dia de Corpus Christi como feriado estadual, a ser celebrado na + * primeira quinta-feira após decorridos sessenta dias do domingo de Páscoa", Easter plus 60, in + * force on its publication, so listed from 2026. It replaces the national optional entry typed + * `"state"`, as the Distrito Federal and Maranhão ones do. Missing up to 2.4.0. + * @see Official: https://portal.stf.jus.br/processos/detalhe.asp?incidente=7434201 + * STF ADI 7898, brought by the CNC against Lei RJ nº 11.002/2025: "julgou improcedente o pedido + * formulado na presente ação direta de inconstitucionalidade ... Plenário, Sessão Virtual de + * 12.6.2026 a 19.6.2026", by unanimity; trânsito em julgado 13/08/2026. Rio de Janeiro's other two + * state holiday laws voided by the STF have no entry: Lei RJ nº 8.174/2018 (second Sunday of May, + * ADI 6133) and Lei RJ nº 8.217/2018 (Ash Wednesday for bank workers only, ADI 6083). * @see Official: http://www.al.rn.leg.br/storage/legislacao//arq5064574f632ec.pdf * Lei RN nº 8.913, de 06/12/2006, Mártires de Cunhaú e Uruaçu (03/10), listed from 2007, the * single entry of Rio Grande do Norte: a "Resumo da Lei" search for "feriado" in the ALRN legislation base @@ -772,6 +785,7 @@ export const STATE_HOLIDAYS: Partial> = { RJ: [ { name: "Carnaval (terça-feira)", easterOffset: -47, since: 2009 }, { name: "São Jorge", day: 23, month: 4, since: 2008 }, + { name: "Corpus Christi", easterOffset: 60, since: 2026 }, { name: CONSCIENCIA_NEGRA_HOLIDAY_NAME, day: 20, diff --git a/src/get-holidays/get-holidays.test.ts b/src/get-holidays/get-holidays.test.ts index 8a267778..5e8cd887 100644 --- a/src/get-holidays/get-holidays.test.ts +++ b/src/get-holidays/get-holidays.test.ts @@ -1580,6 +1580,23 @@ describe("getHolidays", () => { }); }); + test("should replace the national optional Corpus Christi with an RJ state entry from 2026 on, Lei RJ nº 11.002/2025 having been upheld by the STF in ADI 7898, and keep it optional in 2025", () => { + expect( + getHolidays({ year: 2026, stateCode: "RJ" }).filter((h) => h.name === "Corpus Christi"), + ).toEqual([{ name: "Corpus Christi", date: new Date(2026, 5, 4), type: "state" }]); + + expect( + getHolidays({ year: 2025, stateCode: "RJ" }).filter((h) => h.name === "Corpus Christi"), + ).toEqual([{ name: "Corpus Christi", date: new Date(2025, 5, 19), type: "optional" }]); + + expect(isBusinessDay(new Date(2026, 5, 4), { stateCode: "RJ", includeOptional: false })).toBe( + false, + ); + expect(isBusinessDay(new Date(2025, 5, 19), { stateCode: "RJ", includeOptional: false })).toBe( + true, + ); + }); + test("should replace the national optional Corpus Christi with an MA state entry from 2024 on, Lei MA nº 11.539/2021 having been upheld by the TJMA on 06/03/2024, and keep it optional in 2023", () => { expect( getHolidays({ year: 2024, stateCode: "MA" }).filter((h) => h.name === "Corpus Christi"), diff --git a/src/is-business-day/is-business-day.ts b/src/is-business-day/is-business-day.ts index c30dc6a2..2d149fd7 100644 --- a/src/is-business-day/is-business-day.ts +++ b/src/is-business-day/is-business-day.ts @@ -75,10 +75,10 @@ const SATURDAY = 6; * non-string value that stands for "no state" instead. `addBusinessDays`, `subBusinessDays` * and `differenceInBusinessDays` reject the same value with `null`. * - * Three state rules change what `includeOptional: false` answers. The Distrito Federal and, from - * 2024 on, Maranhão declare Corpus Christi a feriado (Lei distrital nº 72/1989, art. 1º parágrafo - * único; Lei MA nº 11.539/2021), so with `stateCode: "DF"` or `"MA"` it is typed `"state"` and - * still counts; Rio de Janeiro declares the Carnaval + * Three state rules change what `includeOptional: false` answers. The Distrito Federal, Maranhão + * from 2024 on and Rio de Janeiro from 2026 on declare Corpus Christi a feriado (Lei distrital nº + * 72/1989, art. 1º parágrafo único; Lei MA nº 11.539/2021; Lei RJ nº 11.002/2025), so with + * `stateCode: "DF"`, `"MA"` or `"RJ"` it is typed `"state"` and still counts; Rio de Janeiro declares the Carnaval * Tuesday a feriado estadual (Lei RJ nº 5.243/2008, from 2009 on), so with `stateCode: "RJ"` it * still counts while the Monday does not; and Santa Catarina's two holidays * are observed on the following Sunday when they fall Monday to Friday (Lei SC nº 18.531/2022), diff --git a/src/is-valid-bank-account/is-valid-bank-account.test.ts b/src/is-valid-bank-account/is-valid-bank-account.test.ts index 7b3f108b..d4de7c22 100644 --- a/src/is-valid-bank-account/is-valid-bank-account.test.ts +++ b/src/is-valid-bank-account/is-valid-bank-account.test.ts @@ -689,6 +689,47 @@ describe("isValidBankAccount", () => { }), ).toBe(true); }); + + test("when a 12 digit account carries its own digit, the Caixa CNAB 400 note NE051 example (000000109990, digit 6)", () => { + expect( + isValidBankAccount({ + bankCode: "104", + agency: "0161", + account: "000000109990", + digit: "6", + }), + ).toBe(true); + }); + + test("when a 12 digit account carries the agency/account digit, the Caixa CNAB 400 note NE052 example (0161 + 000000109990, digit 5)", () => { + expect( + isValidBankAccount({ + bankCode: "104", + agency: "0161", + account: "000000109990", + digit: "5", + }), + ).toBe(true); + }); + + test("but not when a 12 digit account carries neither digit", () => { + for (const digit of ["0", "1", "2", "3", "4", "7", "8", "9"]) { + expect( + isValidBankAccount({ bankCode: "104", agency: "0161", account: "000000109990", digit }), + ).toBe(false); + } + }); + + test("when a 12 digit account's digit comes out above 9 it is 0 (account 000000000006: sum 12, remainder 1, 11 - 1 = 10)", () => { + expect( + isValidBankAccount({ + bankCode: "104", + agency: "0000", + account: "000000000006", + digit: "0", + }), + ).toBe(true); + }); }); describe("generic validation with a two digit check", () => { diff --git a/src/is-valid-bank-account/is-valid-bank-account.ts b/src/is-valid-bank-account/is-valid-bank-account.ts index d16fe044..7d1fabd4 100644 --- a/src/is-valid-bank-account/is-valid-bank-account.ts +++ b/src/is-valid-bank-account/is-valid-bank-account.ts @@ -84,12 +84,29 @@ const banrisulDigits: BankAccountDigits = (_agency, account) => { return [String(11 - remainder)]; }; -const caixaDigits: BankAccountDigits = (agency, account) => { - const digit = mod11(agency + account, { variant: "bank" }); +const CAIXA_ACCOUNT_LENGTH = 12; - return [String(digit === 10 ? 0 : digit)]; +const caixaModulus11 = (value: string): string => { + const digit = mod11(value, { variant: "bank" }); + + return String(digit === 10 ? 0 : digit); }; +/** + * The Caixa check digits of an account. A 12 digit account is the format the Caixa layouts + * describe, "sem operação", and takes either of the two digits they define: the account's own + * (note NE051) and the agency/account one (NE052). An 11 digit account is operação (3 digits) + + * conta (8 digits), the older format, with the agency/account digit of the compendium. + * + * @param {string} agency - The 4 digit agency. + * @param {string} account - The 11 or 12 digit account. + * @returns {string[]} The check digits the account may carry. + */ +const caixaDigits: BankAccountDigits = (agency, account) => + account.length === CAIXA_ACCOUNT_LENGTH + ? [caixaModulus11(account), caixaModulus11(agency + account)] + : [caixaModulus11(agency + account)]; + const bradescoDigits: BankAccountDigits = (_agency, account) => { const digit = mod11(account, { variant: "bank", maxWeight: 7 }); @@ -150,7 +167,7 @@ const BANK_RULES: Record = { minAgencyLength: 4, maxAgencyLength: 4, minAccountLength: 11, - maxAccountLength: 11, + maxAccountLength: 12, digits: caixaDigits, }, "237": { @@ -253,10 +270,19 @@ const sanitizeCheckDigit = (value: string): string => * Bradesco (237), Itaú Unibanco (341), HSBC/Kirton (399) and Citibank (745), with the rules * of the "Regras de Validação de dígito verificador de agência e conta corrente" compendium * of Icatu Seguros, and Nubank (260), with a Verhoeff check digit. No act of the Banco - * Central, of another government body or of Febraban sets any of these rules: the compendium - * is a private compilation of the rules of each bank, and Nubank publishes no rule at all; - * its Verhoeff digit is the one the open source validators listed below derived from real - * accounts. The FEBRABAN Layout Padrão CNAB 240 v11.0 (11/09/2026), notes G009, G011 and G012, + * Central, of another government body or of Febraban sets any of these rules, and the + * compendium is a private compilation of the rules of each bank. Three banks publish their own + * in their layout manuals, cited below, and the rules here match them: the Caixa both of its + * digits, over a 12 digit account (module 11, weights 2 to 9 from the right, a result above 9 + * giving 0; its example, account 000000109990 with digit 6 and agency 0161 with + * agency/account digit 5, is accepted), Santander the account digit (its example, agency 2001 + * and account 01 038237 with digit 7, is accepted), and Banco do Brasil only the agency digit + * (module 11, weights 9 to 2 from the right, remainder 10 giving "X"), the account digit being + * "módulo 11" and the rule here the same one. The Caixa layouts describe the account with 12 + * digits, "sem operação"; the older operação (3 digits) + conta (8 digits) form keeps the + * compendium rule. Up to 2.4.0 a 12 digit Caixa account was rejected. Nubank publishes no rule + * at all; its Verhoeff digit is the one the open source validators listed below derived from + * real accounts. The FEBRABAN Layout Padrão CNAB 240 v11.0 (11/09/2026), notes G009, G011 and G012, * defines each of the agency, account and agency/account check digits only as a "código adotado * pelo Banco responsável pela conta corrente", allows it to be alphanumeric and a 2 position * account digit, and gives no algorithm; the DICT API of the Banco Central takes the account @@ -273,7 +299,8 @@ const sanitizeCheckDigit = (value: string): string => * @param {IsValidBankAccountParams} params - The bank account parameters. * @param {string} params.bankCode - The bank code (3 digits), as published by Banco Central. * @param {string} params.agency - The agency number (1-5 digits). - * @param {string} params.account - The account number (1-13 digits). For Caixa, operação + conta. + * @param {string} params.account - The account number (1-13 digits). For Caixa, the 12 digit + * account, or operação + conta (11 digits). * @param {string} params.digit - The verification digit (1-2 digits, or "X" for Banco do Brasil and "P" for Bradesco). * @returns {boolean} True if the bank account is valid, false otherwise. * @@ -293,6 +320,18 @@ const sanitizeCheckDigit = (value: string): string => * @see Official: https://cmsarquivos.febraban.org.br/Arquivos/documentos/PDF/Layout%20padrao%20CNAB240%20V%2011_0%20-%202026_09_11.pdf * FEBRABAN, Layout Padrão CNAB 240 v11.0 (11/09/2026), notes G009 to G012: "Código adotado pelo * Banco", with no algorithm. + * @see Official: https://www.caixa.gov.br/Downloads/cobranca-caixa/Manual_de_Leiaute_de_Arquivo_Eletronico_CNAB_400.pdf + * Caixa, Leiaute CNAB 400 – Cobrança Bancária CAIXA – SIGCB, 67.126 v029 (May 2024), notes NE051 + * (account digit) and NE052 (agency/account digit), each with a worked example. + * @see Official: https://www.caixa.gov.br/Downloads/cobranca-caixa/Manual_de_Leiaute_de_Arquivo_Eletronico_CNAB_240.pdf + * Caixa, Leiaute CNAB 240 – Cobrança Bancária CAIXA, 67.118 v031 (February 2024), note G011, the + * same account digit example. + * @see Official: https://www.santander.com.br/layout-de-arquivos + * Santander, Layout de Arquivo – Débito Automático – 150 posições, versão 08 (April 2026), "Cálculo + * do Dígito Verificador (DV) da Conta Corrente". + * @see Official: https://www.bb.com.br/docs/pub/emp/empl/dwn/Doc5175Bloqueto.pdf + * Banco do Brasil, Especificações Técnicas para Confecção de Boleto de Pagamento (May 2019), + * Anexo XI, the módulo 11 of the "prefixo da agência". * @see Based on: https://github.com/eduardokum/laravel-boleto/blob/master/manuais/Regras%20Validacao%20Conta%20Corrente%20VI_EPS.pdf * Icatu Seguros compendium of per bank agency/account check digit rules. * @see Based on: https://github.com/ajmiciano/banktools-br/tree/master/lib/banktools-br/banks