Repository: moov-io/ach
Pull request #1387 (2024-02-29 17:52:10)

fix: read additional corrected data on IAT corrections
Noticed while working on https://github.com/moov-io/ach/pull/1386 


Patch:
diff --git a/addenda98.go b/addenda98.go
index 0aa8d6c1..b6409753 100644
--- a/addenda98.go
+++ b/addenda98.go
@@ -42,6 +42,10 @@ type Addenda98 struct {
 	OriginalDFI string `json:"originalDFI"`
 	// CorrectedData is the corrected data
 	CorrectedData string `json:"correctedData"`
+
+	// iatCorrectedData is a field containing the additional space allowed in IAT Correction Addenda records
+	iatCorrectedData string
+
 	// TraceNumber matches the Entry Detail Trace Number of the entry being returned.
 	//
 	// Use TraceNumberField for a properly formatted string representation.
@@ -118,12 +122,20 @@ func (addenda98 *Addenda98) Parse(record string) {
 		case 21:
 			// 7-21
 			addenda98.OriginalTrace = strings.TrimSpace(reset())
+		case 27:
+			reset()
 		case 35:
 			// 28-35
 			addenda98.OriginalDFI = addenda98.parseStringField(reset())
 		case 64:
 			// 36-64
 			addenda98.CorrectedData = strings.TrimSpace(reset())
+		case 70:
+			// 65-70 (Reserved for all except IAT Corrections)
+			addenda98.iatCorrectedData = strings.TrimSpace(reset())
+		case 79:
+			// Reserved
+			reset()
 		case 94:
 			// 80-94
 			addenda98.TraceNumber = strings.TrimSpace(reset())
@@ -187,7 +199,16 @@ func (addenda98 *Addenda98) OriginalDFIField() string {
 
 // CorrectedDataField returns a space padded CorrectedData string
 func (addenda98 *Addenda98) CorrectedDataField() string {
-	return addenda98.alphaField(addenda98.CorrectedData, 29)
+	if addenda98.iatCorrectedData == "" {
+		return addenda98.alphaField(addenda98.CorrectedData, 29)
+	}
+	return addenda98.IATCorrectedDataField()
+}
+
+func (addenda98 *Addenda98) IATCorrectedDataField() string {
+	out := addenda98.alphaField(addenda98.CorrectedData, 29)
+	out += addenda98.alphaField(addenda98.iatCorrectedData, 6)
+	return out
 }
 
 // TraceNumberField returns a zero padded traceNumber string
@@ -275,17 +296,18 @@ func (addenda98 *Addenda98) ParseCorrectedData() *CorrectedData {
 	if cc == nil {
 		return nil
 	}
+	data := addenda98.IATCorrectedDataField()
 	switch cc.Code {
 	case "C01": // Incorrect DFI Account Number
-		if v := first(17, addenda98.CorrectedData); v != "" {
+		if v := first(17, data); v != "" {
 			return &CorrectedData{AccountNumber: v}
 		}
 	case "C02": // Incorrect Routing Number
-		if v := first(9, addenda98.CorrectedData); v != "" {
+		if v := first(9, data); v != "" {
 			return &CorrectedData{RoutingNumber: v}
 		}
 	case "C03": // Incorrect Routing Number and Incorrect DFI Account Number
-		parts := strings.Fields(addenda98.CorrectedData)
+		parts := strings.Fields(data)
 		if len(parts) == 2 {
 			return &CorrectedData{
 				RoutingNumber: parts[0],
@@ -293,15 +315,15 @@ func (addenda98 *Addenda98) ParseCorrectedData() *CorrectedData {
 			}
 		}
 	case "C04": // Incorrect Individual Name
-		if v := first(22, addenda98.CorrectedData); v != "" {
+		if v := first(22, data); v != "" {
 			return &CorrectedData{Name: v}
 		}
 	case "C05": // Incorrect Transaction Code
-		if n, err := strconv.Atoi(first(2, addenda98.CorrectedData)); err == nil {
+		if n, err := strconv.Atoi(first(2, data)); err == nil {
 			return &CorrectedData{TransactionCode: n}
 		}
 	case "C06": // Incorrect DFI Account Number and Incorrect Transaction Code
-		parts := strings.Fields(addenda98.CorrectedData)
+		parts := strings.Fields(data)
 		if len(parts) == 2 {
 			if n, err := strconv.Atoi(parts[1]); err == nil {
 				return &CorrectedData{
@@ -312,12 +334,12 @@ func (addenda98 *Addenda98) ParseCorrectedData() *CorrectedData {
 		}
 	case "C07": // Incorrect Routing Number, Incorrect DFI Account Number, and Incorrect Tranaction Code
 		var cd CorrectedData
-		if n := len(addenda98.CorrectedData); n > 9 {
-			cd.RoutingNumber = addenda98.CorrectedData[:9]
+		if n := len(data); n > 9 {
+			cd.RoutingNumber = data[:9]
 		} else {
 			return nil
 		}
-		parts := strings.Fields(addenda98.CorrectedData[9:])
+		parts := strings.Fields(data[9:])
 		if len(parts) == 2 {
 			if n, err := strconv.Atoi(parts[1]); err == nil {
 				cd.AccountNumber = parts[0]
@@ -328,7 +350,7 @@ func (addenda98 *Addenda98) ParseCorrectedData() *CorrectedData {
 			return nil
 		}
 	case "C09": // Incorrect Individual Identification Number
-		if v := first(22, addenda98.CorrectedData); v != "" {
+		if v := first(22, data); v != "" {
 			return &CorrectedData{Identification: v}
 		}
 	}
diff --git a/go.mod b/go.mod
index e7fdf7f5..f7a540fa 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/juju/ansiterm v1.0.0
 	github.com/moov-io/base v0.48.5
 	github.com/moov-io/iso4217 v0.3.0
-	github.com/prometheus/client_golang v1.19.0
+	github.com/prometheus/client_golang v1.18.0
 	github.com/stretchr/testify v1.8.4
 	golang.org/x/net v0.21.0
 	golang.org/x/text v0.14.0
@@ -25,9 +25,10 @@ require (
 	github.com/lunixbochs/vtclean v1.0.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.19 // indirect
+	github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/prometheus/client_model v0.5.0 // indirect
-	github.com/prometheus/common v0.48.0 // indirect
+	github.com/prometheus/common v0.45.0 // indirect
 	github.com/prometheus/procfs v0.12.0 // indirect
 	github.com/rickar/cal/v2 v2.1.13 // indirect
 	golang.org/x/sys v0.17.0 // indirect
diff --git a/go.sum b/go.sum
index 28dc8e3a..7466a0b9 100644
--- a/go.sum
+++ b/go.sum
@@ -15,7 +15,7 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
 github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
 github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
 github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
@@ -36,18 +36,20 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
+github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
 github.com/moov-io/base v0.48.5 h1:QaTyTo6eFFFV35R9l/GdePQN40IJti9knD5hqdWPnnM=
 github.com/moov-io/base v0.48.5/go.mod h1:D5ZV9COV/qtCjTQuYpq7gGInCk64AhOQI6UY4kt4Rq8=
 github.com/moov-io/iso4217 v0.3.0 h1:xAxeJEn7QIpEy971eElEGiaLNdj81UwOcCcvE6j2ZAo=
 github.com/moov-io/iso4217 v0.3.0/go.mod h1:/pF1mBInDSJtwyWkLNr8EMdIn1zIl2cyjaVi4Xhpd44=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
-github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
+github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
+github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
 github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
 github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
-github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
-github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
+github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
+github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
 github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
 github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
 github.com/rickar/cal/v2 v2.1.13 h1:FENBPXxDPyL1OWGf9ZdpWGcEiGoSjt0UZED8VOxvK0c=


Test patch:
diff --git a/addenda98_test.go b/addenda98_test.go
index c9f2c21c..9677ad1a 100644
--- a/addenda98_test.go
+++ b/addenda98_test.go
@@ -18,6 +18,7 @@
 package ach
 
 import (
+	"strings"
 	"testing"
 
 	"github.com/moov-io/base"
@@ -434,3 +435,54 @@ func TestCorrectedData__WriteCorrectionData(t *testing.T) {
 	require.Equal(t, "FooBar                       ", WriteCorrectionData("C09", data))
 
 }
+
+func TestIATCorrectedData(t *testing.T) {
+	t.Run("C01", func(t *testing.T) {
+		addenda98 := NewAddenda98()
+		line := "798C01123456780000001      34567891227-13569                                   345678910963842"
+		addenda98.Parse(line)
+
+		require.Equal(t, "98", addenda98.TypeCode)
+		require.Equal(t, "C01", addenda98.ChangeCode)
+		require.Equal(t, "123456780000001", addenda98.OriginalTrace)
+		require.Equal(t, "34567891", addenda98.OriginalDFI)
+
+		correctedData := "227-13569"
+		require.Equal(t, correctedData, addenda98.CorrectedData)
+		require.Equal(t, "345678910963842", addenda98.TraceNumber)
+
+		//
+		expected := correctedData + strings.Repeat(" ", 29-len(correctedData))
+		require.Equal(t, expected, addenda98.CorrectedDataField())
+		require.Equal(t, line, addenda98.String())
+
+		//
+		expected = correctedData + strings.Repeat(" ", 35-len(correctedData))
+		require.Equal(t, expected, addenda98.IATCorrectedDataField())
+		require.Equal(t, line, addenda98.String())
+	})
+
+	t.Run("C07", func(t *testing.T) {
+		addenda98 := NewAddenda98()
+		line := "798C07123456780000001      34567891227-13569                                   345678910963842"
+		addenda98.Parse(line)
+
+		data := &CorrectedData{RoutingNumber: "987654320", AccountNumber: "5421", TransactionCode: 32}
+		addenda98.CorrectedData = WriteCorrectionData("C07", data)
+		require.Equal(t, "9876543205421              32", addenda98.CorrectedDataField())
+		require.Equal(t, "9876543205421              32      ", addenda98.IATCorrectedDataField())
+
+		parsed := addenda98.ParseCorrectedData()
+		require.NotNil(t, parsed)
+		require.Equal(t, *data, *parsed)
+
+		// Now with 32 at the end
+		line = "798C07123456780000001      345678919876543205421                    32         345678910963842"
+		addenda98.Parse(line)
+		require.Equal(t, "9876543205421                32    ", addenda98.IATCorrectedDataField())
+
+		parsed = addenda98.ParseCorrectedData()
+		require.NotNil(t, parsed)
+		require.Equal(t, *data, *parsed)
+	})
+}