Skip to content

Commit f4aceab

Browse files
committed
CBU (Clave Bancaria Uniforme, Argentine bank account number)
1 parent 74972a7 commit f4aceab

File tree

6 files changed

+237
-0
lines changed

6 files changed

+237
-0
lines changed

lib/ar/cbu.ml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
open Tools
2+
3+
exception Invalid_length
4+
exception Invalid_format
5+
exception Invalid_checksum
6+
7+
let compact number =
8+
Utils.clean number " -" |> String.trim
9+
10+
let calc_check_digit number =
11+
let weights = [| 3; 1; 7; 9 |] in
12+
let len = String.length number in
13+
let rec sum i acc =
14+
if i < len then
15+
let w = weights.((len - 1 - i) mod 4) in
16+
let n = int_of_char number.[i] - int_of_char '0' in
17+
sum (i + 1) (acc + (w * n))
18+
else acc
19+
in
20+
string_of_int ((10 - (sum 0 0)) mod 10)
21+
22+
let validate number =
23+
let number = compact number in
24+
if String.length number <> 22 then raise Invalid_length
25+
else if not (Utils.is_digits number) then raise Invalid_format
26+
else if number.[7] <> (calc_check_digit (String.sub number 0 7)).[0] then
27+
raise Invalid_checksum
28+
else if
29+
number.[String.length number - 1]
30+
<> (calc_check_digit (String.sub number 8 (String.length number - 9))).[0]
31+
then raise Invalid_checksum
32+
else number
33+
34+
let is_valid number =
35+
try
36+
ignore (validate number);
37+
true
38+
with Invalid_format | Invalid_length | Invalid_checksum -> false
39+
40+
let format number =
41+
let number = compact number in
42+
String.sub number 0 8 ^ " " ^ String.sub number 8 14

lib/ar/cbu.mli

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
(*
2+
CBU (Clave Bancaria Uniforme, Argentine bank account number).
3+
4+
CBU it s a code of the Banks of Argentina to identify customer accounts. The
5+
number consists of 22 digits and consists of a 3 digit bank identifier,
6+
followed by a 4 digit branch identifier, a check digit, a 13 digit account
7+
identifier and another check digit.
8+
9+
More information:
10+
11+
* https://es.wikipedia.org/wiki/Clave_Bancaria_Uniforme
12+
*)
13+
14+
exception Invalid_length
15+
(** Exception raised when the CBU number has an invalid length. *)
16+
17+
exception Invalid_format
18+
(** Exception raised when the CBU number has an invalid format. *)
19+
20+
exception Invalid_checksum
21+
(** Exception raised when the CBU number has an invalid checksum. *)
22+
23+
val validate : string -> string
24+
(** Check if the number is a valid CBU. Returns the normalized number if valid.
25+
@raise Invalid_length if the number length is not 22
26+
@raise Invalid_format if the number contains non-digit characters
27+
@raise Invalid_checksum if either check digit is invalid *)
28+
29+
val is_valid : string -> bool
30+
(** Check if the number is a valid CBU. Returns true if valid, false otherwise. *)
31+
32+
val format : string -> string
33+
(** Reformat the number to the standard presentation format with spaces. *)
34+
35+
val compact : string -> string
36+
(** [compact number] removes spaces and dashes from the number. *)
37+
38+
val calc_check_digit : string -> string
39+
(** [calc_check_digit number] calculates the check digit for the given number sequence
40+
using the CBU algorithm. *)

lib/ar/dune

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(library
2+
(name ar)
3+
(public_name stdnum.ar)
4+
(libraries stdnum.tools))

lib/tools/dune

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
(library
22
(name tools)
3+
(public_name stdnum.tools)
34
(libraries str))

test/ar/dune

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(test
2+
(name test_cbu)
3+
(libraries alcotest stdnum.tools stdnum.ar))

test/ar/test_cbu.ml

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
open Ar.Cbu
2+
open Alcotest
3+
4+
let test_valid_numbers () =
5+
let numbers = [
6+
"0070999020000065706080";
7+
"0110433630043313857683";
8+
"0140339601630201381276";
9+
"0140023601506802625874";
10+
"0440064640000142941092";
11+
"0720146820000001062340";
12+
"0720168020000001183236";
13+
"0720380888000035533968";
14+
"0070034420000002310035";
15+
"0070085620000002598406";
16+
"0070089420000002991793";
17+
"0070090020000004146504";
18+
"0070109530004141775453";
19+
"0070114920000004100700";
20+
"0070274620000003448717";
21+
"0070999020000057705860";
22+
"0110097630009704213797";
23+
"0110102320010200444955";
24+
"0110106130010603111097";
25+
"0110106130010604601847";
26+
"0110125220012510923535";
27+
"0110130620013014594573";
28+
"0110175730017523189801";
29+
"0110204030020409626051";
30+
"0110216320021610025999";
31+
"0110230930023001323933";
32+
"0110230930023008918451";
33+
"0110283520028310814652";
34+
"0110363020036300101822";
35+
"0110377720037700120402";
36+
"0110385220038500036492";
37+
"0110409120040921180719";
38+
"0110424420042410570553";
39+
"0110454130045407688379";
40+
"0110477020047731297428";
41+
"0110508720050800019135";
42+
"0110521620052100223696";
43+
"0110551320055100112719";
44+
"0140313601697100515896";
45+
"0140313601697100557414";
46+
"0140339601630201381276";
47+
"0140351801684605023087";
48+
"0140352501684700733410";
49+
"0140352503684700819149";
50+
"0140369303631000285682";
51+
"0140391403672850026131";
52+
"0140410801680000361629";
53+
"0140417701630000088992";
54+
"0140444301650700088379";
55+
"0140476401626402048153";
56+
"0150501602000120967405";
57+
"0168888100008274410158";
58+
"0168888100000641080265";
59+
"0170074920000030293449";
60+
"0170334220000030367766";
61+
"0200306901000040010097";
62+
"0200348901000000334779";
63+
"0200398411000030044362";
64+
"0200405501000000213951";
65+
"0200451211000030033962";
66+
"0200915901000000274233";
67+
"0340056200560007577005";
68+
"0720000720000001681136";
69+
"0720079388000035942322";
70+
"0720297320000000081418";
71+
"0720402320000002633754";
72+
"0930301810100000992800";
73+
"0930301810100001043132";
74+
"0930310010100014278400";
75+
"0930324720100053299139";
76+
"0930324720100055211111";
77+
"0940099324001313220028";
78+
"1500006000005660447200";
79+
"1500087900051332075196";
80+
"1910119655011901084646";
81+
"1910104255110401549353";
82+
"1910126455012600786400";
83+
"1910186855018601143246";
84+
"1910369755036901130632";
85+
"2850345330000000781858";
86+
"2850353830094127564171";
87+
"2850376730000059833142";
88+
"2850400530094105352671";
89+
"2850536730094125514871";
90+
"2850590940090418135201";
91+
"2850729540000001576069";
92+
"2850732530000002707016";
93+
"2850734940094696942458";
94+
"2850760830094054972021";
95+
"2850882330094054578991";
96+
"3110003611000000537014";
97+
"3110013511000600125046";
98+
"3300542115420000740012";
99+
"3300551315510001836040";
100+
"3860002703000000438381";
101+
"3860011901000020526675";
102+
"3860060703000013990500";
103+
"5729195067928761667584";
104+
"7362966507842824472644";
105+
"9498175528566296510521";
106+
] in
107+
List.iter (fun n ->
108+
check bool (n ^ " should be valid") true (is_valid n)
109+
) numbers
110+
111+
let test_invalid_length () =
112+
Alcotest.check_raises "should raise Invalid_length"
113+
Invalid_length
114+
(fun () -> ignore (validate "285059094009041"))
115+
116+
let test_invalid_format () =
117+
check_raises "should raise Invalid_format"
118+
Invalid_format
119+
(fun () -> ignore "A850590940090418135201")
120+
121+
let test_valid_number () =
122+
check string "should validate correct number"
123+
"0940099324001313220028"
124+
(validate "0940099324001313220028")
125+
126+
let test_invalid_first_part () =
127+
check_raises "should raise Invalid_checksum"
128+
Invalid_checksum
129+
(fun () -> ignore "1940099324001313220028")
130+
131+
let test_invalid_second_part () =
132+
check_raises "should raise Invalid_checksum"
133+
Invalid_checksum
134+
(fun () -> ignore "0940099324001313220038")
135+
136+
let test_cases = [
137+
"CBU validation", [
138+
test_case "valid numbers" `Quick test_valid_numbers;
139+
test_case "invalid length" `Quick test_invalid_length;
140+
test_case "invalid format" `Quick test_invalid_format;
141+
test_case "valid number" `Quick test_valid_number;
142+
test_case "invalid first part" `Quick test_invalid_first_part;
143+
test_case "invalid second part" `Quick test_invalid_second_part;
144+
];
145+
]
146+
147+
let () = run "CBU" test_cases

0 commit comments

Comments
 (0)