Skip to content

CBU (Clave Bancaria Uniforme, Argentine bank account number) #226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
41 changes: 41 additions & 0 deletions lib/ar/cbu.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
open Tools

exception Invalid_length
exception Invalid_format
exception Invalid_checksum

let compact number = Utils.clean number " -" |> String.trim

let calc_check_digit number =
let weights = [| 3; 1; 7; 9 |] in
let len = String.length number in
let rec sum i acc =
if i < len then
let w = weights.((len - 1 - i) mod 4) in
let n = int_of_char number.[i] - int_of_char '0' in
sum (i + 1) (acc + (w * n))
else acc
in
string_of_int ((10 - sum 0 0) mod 10)

let validate number =
let number = compact number in
if String.length number <> 22 then raise Invalid_length
else if not (Utils.is_digits number) then raise Invalid_format
else if number.[7] <> (calc_check_digit (String.sub number 0 7)).[0] then
raise Invalid_checksum
else if
number.[String.length number - 1]
<> (calc_check_digit (String.sub number 8 (String.length number - 9))).[0]
then raise Invalid_checksum
else number

let is_valid number =
try
ignore (validate number);
true
with Invalid_format | Invalid_length | Invalid_checksum -> false

let format number =
let number = compact number in
String.sub number 0 8 ^ " " ^ String.sub number 8 14
40 changes: 40 additions & 0 deletions lib/ar/cbu.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
(*
CBU (Clave Bancaria Uniforme, Argentine bank account number).

CBU it s a code of the Banks of Argentina to identify customer accounts. The
number consists of 22 digits and consists of a 3 digit bank identifier,
followed by a 4 digit branch identifier, a check digit, a 13 digit account
identifier and another check digit.

More information:

* https://es.wikipedia.org/wiki/Clave_Bancaria_Uniforme
*)

exception Invalid_length
(** Exception raised when the CBU number has an invalid length. *)

exception Invalid_format
(** Exception raised when the CBU number has an invalid format. *)

exception Invalid_checksum
(** Exception raised when the CBU number has an invalid checksum. *)

val validate : string -> string
(** Check if the number is a valid CBU. Returns the normalized number if valid.
@raise Invalid_length if the number length is not 22
@raise Invalid_format if the number contains non-digit characters
@raise Invalid_checksum if either check digit is invalid *)

val is_valid : string -> bool
(** Check if the number is a valid CBU. Returns true if valid, false otherwise. *)

val format : string -> string
(** Reformat the number to the standard presentation format with spaces. *)

val compact : string -> string
(** [compact number] removes spaces and dashes from the number. *)

val calc_check_digit : string -> string
(** [calc_check_digit number] calculates the check digit for the given number sequence
using the CBU algorithm. *)
4 changes: 4 additions & 0 deletions lib/ar/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(library
(name ar)
(public_name stdnum.ar)
(libraries stdnum.tools))
1 change: 1 addition & 0 deletions lib/tools/dune
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
(library
(name tools)
(public_name stdnum.tools)
(libraries str))
3 changes: 3 additions & 0 deletions test/ar/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(test
(name test_cbu)
(libraries alcotest stdnum.tools stdnum.ar))
146 changes: 146 additions & 0 deletions test/ar/test_cbu.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
open Ar.Cbu
open Alcotest

let test_valid_numbers () =
let numbers =
[
"0070999020000065706080"
; "0110433630043313857683"
; "0140339601630201381276"
; "0140023601506802625874"
; "0440064640000142941092"
; "0720146820000001062340"
; "0720168020000001183236"
; "0720380888000035533968"
; "0070034420000002310035"
; "0070085620000002598406"
; "0070089420000002991793"
; "0070090020000004146504"
; "0070109530004141775453"
; "0070114920000004100700"
; "0070274620000003448717"
; "0070999020000057705860"
; "0110097630009704213797"
; "0110102320010200444955"
; "0110106130010603111097"
; "0110106130010604601847"
; "0110125220012510923535"
; "0110130620013014594573"
; "0110175730017523189801"
; "0110204030020409626051"
; "0110216320021610025999"
; "0110230930023001323933"
; "0110230930023008918451"
; "0110283520028310814652"
; "0110363020036300101822"
; "0110377720037700120402"
; "0110385220038500036492"
; "0110409120040921180719"
; "0110424420042410570553"
; "0110454130045407688379"
; "0110477020047731297428"
; "0110508720050800019135"
; "0110521620052100223696"
; "0110551320055100112719"
; "0140313601697100515896"
; "0140313601697100557414"
; "0140339601630201381276"
; "0140351801684605023087"
; "0140352501684700733410"
; "0140352503684700819149"
; "0140369303631000285682"
; "0140391403672850026131"
; "0140410801680000361629"
; "0140417701630000088992"
; "0140444301650700088379"
; "0140476401626402048153"
; "0150501602000120967405"
; "0168888100008274410158"
; "0168888100000641080265"
; "0170074920000030293449"
; "0170334220000030367766"
; "0200306901000040010097"
; "0200348901000000334779"
; "0200398411000030044362"
; "0200405501000000213951"
; "0200451211000030033962"
; "0200915901000000274233"
; "0340056200560007577005"
; "0720000720000001681136"
; "0720079388000035942322"
; "0720297320000000081418"
; "0720402320000002633754"
; "0930301810100000992800"
; "0930301810100001043132"
; "0930310010100014278400"
; "0930324720100053299139"
; "0930324720100055211111"
; "0940099324001313220028"
; "1500006000005660447200"
; "1500087900051332075196"
; "1910119655011901084646"
; "1910104255110401549353"
; "1910126455012600786400"
; "1910186855018601143246"
; "1910369755036901130632"
; "2850345330000000781858"
; "2850353830094127564171"
; "2850376730000059833142"
; "2850400530094105352671"
; "2850536730094125514871"
; "2850590940090418135201"
; "2850729540000001576069"
; "2850732530000002707016"
; "2850734940094696942458"
; "2850760830094054972021"
; "2850882330094054578991"
; "3110003611000000537014"
; "3110013511000600125046"
; "3300542115420000740012"
; "3300551315510001836040"
; "3860002703000000438381"
; "3860011901000020526675"
; "3860060703000013990500"
; "5729195067928761667584"
; "7362966507842824472644"
; "9498175528566296510521"
]
in
List.iter
(fun n -> check bool (n ^ " should be valid") true (is_valid n))
numbers

let test_invalid_length () =
Alcotest.check_raises "should raise Invalid_length" Invalid_length (fun () ->
ignore (validate "285059094009041"))

let test_invalid_format () =
check_raises "should raise Invalid_format" Invalid_format (fun () ->
ignore "A850590940090418135201")

let test_valid_number () =
check string "should validate correct number" "0940099324001313220028"
(validate "0940099324001313220028")

let test_invalid_first_part () =
check_raises "should raise Invalid_checksum" Invalid_checksum (fun () ->
ignore "1940099324001313220028")

let test_invalid_second_part () =
check_raises "should raise Invalid_checksum" Invalid_checksum (fun () ->
ignore "0940099324001313220038")

let test_cases =
[
( "CBU validation"
, [
test_case "valid numbers" `Quick test_valid_numbers
; test_case "invalid length" `Quick test_invalid_length
; test_case "invalid format" `Quick test_invalid_format
; test_case "valid number" `Quick test_valid_number
; test_case "invalid first part" `Quick test_invalid_first_part
; test_case "invalid second part" `Quick test_invalid_second_part
] )
]

let () = run "CBU" test_cases
Loading