Repository: moov-io/ach
Pull request #1133 (2022-11-16 14:22:37)

feat: expose CalculateCheckDigit
This will make it easier for folks to calculate when they're only given ODFI/RDFI Identification values.


Patch:
diff --git a/advEntryDetail.go b/advEntryDetail.go
index 7e141e8a..192147ae 100644
--- a/advEntryDetail.go
+++ b/advEntryDetail.go
@@ -204,7 +204,7 @@ func (ed *ADVEntryDetail) Validate() error {
 	if err := ed.isAlphanumeric(ed.ACHOperatorRoutingNumber); err != nil {
 		return fieldError("ACHOperatorRoutingNumber", err, ed.ACHOperatorRoutingNumber)
 	}
-	calculated := ed.CalculateCheckDigit(ed.RDFIIdentificationField())
+	calculated := CalculateCheckDigit(ed.RDFIIdentificationField())
 
 	edCheckDigit, _ := strconv.Atoi(ed.CheckDigit)
 
diff --git a/entryDetail.go b/entryDetail.go
index cc911578..67265c16 100644
--- a/entryDetail.go
+++ b/entryDetail.go
@@ -288,7 +288,7 @@ func (ed *EntryDetail) Validate() error {
 	}
 
 	if ed.validateOpts == nil || !ed.validateOpts.AllowInvalidCheckDigit {
-		calculated := ed.CalculateCheckDigit(ed.RDFIIdentificationField())
+		calculated := CalculateCheckDigit(ed.RDFIIdentificationField())
 
 		edCheckDigit, err := strconv.Atoi(ed.CheckDigit)
 		if err != nil {
diff --git a/iatEntryDetail.go b/iatEntryDetail.go
index c20e1692..bfb74507 100644
--- a/iatEntryDetail.go
+++ b/iatEntryDetail.go
@@ -223,7 +223,7 @@ func (iatEd *IATEntryDetail) Validate() error {
 		return fieldError("DFIAccountNumber", err, iatEd.DFIAccountNumber)
 	}
 	// CheckDigit calculations
-	calculated := iatEd.CalculateCheckDigit(iatEd.RDFIIdentificationField())
+	calculated := CalculateCheckDigit(iatEd.RDFIIdentificationField())
 
 	edCheckDigit, err := strconv.Atoi(iatEd.CheckDigit)
 	if err != nil {
diff --git a/validators.go b/validators.go
index 170f62dc..6e8e2acc 100644
--- a/validators.go
+++ b/validators.go
@@ -429,7 +429,7 @@ func (v *validator) isAlphanumeric(s string) error {
 // Add the results of the eight multiplications
 // Subtract the sum from the next highest multiple of 10.
 // The result is the Check Digit
-func (v *validator) CalculateCheckDigit(routingNumber string) int {
+func CalculateCheckDigit(routingNumber string) int {
 	if n := utf8.RuneCountInString(routingNumber); n != 8 && n != 9 {
 		return -1
 	}
@@ -458,7 +458,11 @@ func (v *validator) CalculateCheckDigit(routingNumber string) int {
 	n, _ = strconv.Atoi(routeIndex[7])
 	sum = sum + (n * 7)
 
-	return v.roundUp10(sum) - sum
+	return roundUp10(sum) - sum
+}
+
+func (v *validator) CalculateCheckDigit(routingNumber string) int {
+	return CalculateCheckDigit(routingNumber)
 }
 
 // CheckRoutingNumber returns a nil error if the provided routingNumber is valid according to
@@ -471,8 +475,7 @@ func CheckRoutingNumber(routingNumber string) error {
 		return fmt.Errorf("invalid routing number length of %d", n)
 	}
 
-	v := new(validator)
-	check := fmt.Sprintf("%d", v.CalculateCheckDigit(routingNumber))
+	check := fmt.Sprintf("%d", CalculateCheckDigit(routingNumber))
 	last := string(routingNumber[len(routingNumber)-1])
 	if check != last {
 		return fmt.Errorf("routing number checksum mismatch: expected %s but got %s", check, last)
@@ -481,7 +484,7 @@ func CheckRoutingNumber(routingNumber string) error {
 }
 
 // roundUp10 round number up to the next ten spot.
-func (v *validator) roundUp10(n int) int {
+func roundUp10(n int) int {
 	return int(math.Ceil(float64(n)/10.0)) * 10
 }
 


Test patch:
diff --git a/validators_test.go b/validators_test.go
index 88a25e2b..03e47d8a 100644
--- a/validators_test.go
+++ b/validators_test.go
@@ -38,9 +38,8 @@ func TestValidators__checkDigit(t *testing.T) {
 		"10200007": 6, // Wells Fargo - Colorado
 	}
 
-	v := validator{}
 	for rtn, check := range cases {
-		answer := v.CalculateCheckDigit(rtn)
+		answer := CalculateCheckDigit(rtn)
 		if check != answer {
 			t.Errorf("input=%s answer=%d expected=%d", rtn, answer, check)
 		}