Repository: moov-io/ach
Pull request #1478 (2024-08-28 13:41:16)

fix: ENR batches should use prenote TransactionCodes
Fixes: https://github.com/moov-io/ach/issues/1172


Patch:
diff --git a/batchENR.go b/batchENR.go
index 3b2f5279..1c441d51 100644
--- a/batchENR.go
+++ b/batchENR.go
@@ -66,15 +66,15 @@ func (batch *BatchENR) Validate() error {
 		}
 
 		switch entry.TransactionCode {
-		case CheckingCredit, CheckingDebit, SavingsCredit, SavingsDebit:
+		case CheckingPrenoteCredit, SavingsPrenoteCredit:
 			// nothing
 		default:
 			return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode)
 		}
-		// // Verify the Amount is valid for SEC code and TransactionCode
-		// if err := batch.ValidAmountForCodes(entry); err != nil { // TODO(adam): https://github.com/moov-io/ach/issues/1172
-		// 	return err
-		// }
+		// Verify the Amount is valid for SEC code and TransactionCode
+		if err := batch.ValidAmountForCodes(entry); err != nil {
+			return err
+		}
 		// Verify the TransactionCode is valid for a ServiceClassCode
 		if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
 			return err
@@ -132,7 +132,7 @@ type ENRPaymentInformation struct {
 	EnrolleeClassificationCode string
 }
 
-func (info *ENRPaymentInformation) String() string {
+func (info ENRPaymentInformation) String() string {
 	// Stretch the companies name across two fields
 	var individualName string
 	if strings.EqualFold(info.EnrolleeClassificationCode, "B") {
@@ -165,11 +165,15 @@ func (info *ENRPaymentInformation) String() string {
 		info.EnrolleeClassificationCode)
 }
 
-// ParsePaymentInformation returns an ENRPaymentInformation for a given Addenda05 record. The information is parsed from the addenda's
+// ParseENRPaymentInformation returns an ENRPaymentInformation for a given Addenda05 record. The information is parsed from the addenda's
 // PaymentRelatedInformation field.
 //
 // The returned information is not validated for correctness.
-func (batch *BatchENR) ParsePaymentInformation(addenda05 *Addenda05) (*ENRPaymentInformation, error) {
+func ParseENRPaymentInformation(addenda05 *Addenda05) (*ENRPaymentInformation, error) {
+	if addenda05 == nil {
+		return nil, nil
+	}
+
 	parts := strings.Split(strings.TrimSuffix(addenda05.PaymentRelatedInformation, `\`), "*") // PaymentRelatedInformation is terminated by '\'
 	if len(parts) != 8 {
 		return nil, fmt.Errorf("ENR: unable to parse Addenda05 (%s) PaymentRelatedInformation", addenda05.ID)
diff --git a/cmd/achcli/describe/file.go b/cmd/achcli/describe/file.go
index fb12a803..545feda8 100644
--- a/cmd/achcli/describe/file.go
+++ b/cmd/achcli/describe/file.go
@@ -246,9 +246,9 @@ func dumpAddenda05(w *tabwriter.Writer, batch ach.Batcher, a *ach.Addenda05, opt
 
 	paymentRelatedInfo := a.PaymentRelatedInformationField()
 
-	switch b := batch.(type) {
+	switch batch.(type) {
 	case *ach.BatchENR:
-		paymentInfo, _ := b.ParsePaymentInformation(a)
+		paymentInfo, _ := ach.ParseENRPaymentInformation(a)
 		if paymentInfo != nil {
 			if opts.MaskNames {
 				paymentInfo.IndividualName = maskName(paymentInfo.IndividualName)


Test patch:
diff --git a/batchENR_test.go b/batchENR_test.go
index 9d0f5891..9609e33f 100644
--- a/batchENR_test.go
+++ b/batchENR_test.go
@@ -39,7 +39,7 @@ func mockBatchENRHeader() *BatchHeader {
 // mockENREntryDetail creates a ENR entry detail
 func mockENREntryDetail() *EntryDetail {
 	entry := NewEntryDetail()
-	entry.TransactionCode = CheckingCredit
+	entry.TransactionCode = CheckingPrenoteCredit
 	entry.SetRDFI("031300012")
 	entry.DFIAccountNumber = "744-5678-99"
 	entry.Amount = 0
@@ -267,7 +267,7 @@ func TestBatchENR__PaymentInformation(t *testing.T) {
 		t.Fatal(err)
 	}
 	addenda05 := batch.GetEntries()[0].Addenda05[0]
-	info, err := batch.ParsePaymentInformation(addenda05)
+	info, err := ParseENRPaymentInformation(addenda05)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -294,7 +294,6 @@ func TestBatchENR__PaymentInformation(t *testing.T) {
 		t.Errorf("EnrolleeClassificationCode: %v", v)
 	}
 
-	b := &BatchENR{}
 	addenda05 = NewAddenda05()
 
 	t.Run("Nacha Examples", func(t *testing.T) {
@@ -310,7 +309,7 @@ func TestBatchENR__PaymentInformation(t *testing.T) {
 			EnrolleeClassificationCode: "0",
 		}
 		addenda05.PaymentRelatedInformation = input
-		parsed, err := b.ParsePaymentInformation(addenda05)
+		parsed, err := ParseENRPaymentInformation(addenda05)
 		require.NoError(t, err)
 		require.Equal(t, expected, parsed)
 		require.Equal(t, input, parsed.String())
@@ -318,7 +317,7 @@ func TestBatchENR__PaymentInformation(t *testing.T) {
 		// Consumer
 		input = `27*12200004*3*123987654321*777777777*DOE*JOHN*A\`
 		addenda05.PaymentRelatedInformation = input
-		parsed, err = b.ParsePaymentInformation(addenda05)
+		parsed, err = ParseENRPaymentInformation(addenda05)
 		require.NoError(t, err)
 
 		expected = &ENRPaymentInformation{
@@ -336,7 +335,7 @@ func TestBatchENR__PaymentInformation(t *testing.T) {
 		// Company
 		input = `27*12200004*3*987654321123*876543210*ABCELECTRONICIN*DUSTRIE*B\`
 		addenda05.PaymentRelatedInformation = input
-		parsed, err = b.ParsePaymentInformation(addenda05)
+		parsed, err = ParseENRPaymentInformation(addenda05)
 		require.NoError(t, err)
 
 		expected = &ENRPaymentInformation{
@@ -354,7 +353,7 @@ func TestBatchENR__PaymentInformation(t *testing.T) {
 		// Company
 		input = `27*12200004*3*987654321123*876543210*ELECTRIC**B\`
 		addenda05.PaymentRelatedInformation = input
-		parsed, err = b.ParsePaymentInformation(addenda05)
+		parsed, err = ParseENRPaymentInformation(addenda05)
 		require.NoError(t, err)
 
 		expected = &ENRPaymentInformation{
diff --git a/examples/example_enrWrite_test.go b/examples/example_enrWrite_test.go
index 6974c925..424d7597 100644
--- a/examples/example_enrWrite_test.go
+++ b/examples/example_enrWrite_test.go
@@ -29,7 +29,7 @@ func Example_enrWrite() {
 	fh := mockFileHeader()
 
 	bh := ach.NewBatchHeader()
-	bh.ServiceClassCode = ach.DebitsOnly
+	bh.ServiceClassCode = ach.CreditsOnly
 	bh.CompanyName = "Name on Account"
 	bh.CompanyIdentification = fh.ImmediateOrigin
 	bh.StandardEntryClassCode = ach.ENR
@@ -37,7 +37,7 @@ func Example_enrWrite() {
 	bh.ODFIIdentification = "23138010"
 
 	entry := ach.NewEntryDetail()
-	entry.TransactionCode = ach.CheckingDebit
+	entry.TransactionCode = ach.CheckingPrenoteCredit
 	entry.SetRDFI("031300012")
 	entry.DFIAccountNumber = "744-5678-99"
 	entry.Amount = 0
@@ -74,9 +74,9 @@ func Example_enrWrite() {
 
 	// Output:
 	// 101 031300012 2313801041908161055A094101Federal Reserve Bank   My Bank Name           12345678
-	// 5225Name on Account                     231380104 ENRAUTOENROLL               1231380100000001
-	// 627031300012744-5678-99      0000000000031300010000001Best. #1                1231380100000001
+	// 5220Name on Account                     231380104 ENRAUTOENROLL               1231380100000001
+	// 623031300012744-5678-99      0000000000031300010000001Best. #1                1231380100000001
 	// 70522*12200004*3*123987654321*777777777*DOE*JOHN*1\                                00010000001
-	// 82250000020003130001000000000000000000000000231380104                          231380100000001
+	// 82200000020003130001000000000000000000000000231380104                          231380100000001
 	// 9000001000001000000020003130001000000000000000000000000
 }
diff --git a/examples/testdata/enr-read.ach b/examples/testdata/enr-read.ach
index fa440215..554de4ff 100644
--- a/examples/testdata/enr-read.ach
+++ b/examples/testdata/enr-read.ach
@@ -1,10 +1,10 @@
 101 03130001202313801041908161055A094101Federal Reserve Bank   My Bank Name           12345678
-5225Name on Account                     231380104 ENRAUTOENROLL               1231380100000001
-627031300012744-5678-99      0000000000031300010000001Best. #1                1231380100000001
+5220Name on Account                     231380104 ENRAUTOENROLL               1231380100000001
+623031300012744-5678-99      0000000000031300010000001Best. #1                1231380100000001
 70522*12200004*3*123987654321*777777777*DOE*JOHN*1\                                00010000001
-82250000020003130001000000000000000000000000231380104                          231380100000001
+82200000020003130001000000000000000000000000231380104                          231380100000001
 9000001000001000000020003130001000000000000000000000000                                       
 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
-9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
\ No newline at end of file
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/ach-enr-read/enr-read.ach b/test/ach-enr-read/enr-read.ach
index 7a89299c..67d7ab98 100644
--- a/test/ach-enr-read/enr-read.ach
+++ b/test/ach-enr-read/enr-read.ach
@@ -1,8 +1,8 @@
 101 031300012 2313801041811260000A094101Federal Reserve Bank   My Bank Name                   
-5225Name on Account                     231380104 ENRAUTOENROLL               1231380100000001
-627031300012744-5678-99      0000000000031300010000001Best. #1                1231380100000001
+5220Name on Account                     231380104 ENRAUTOENROLL               1231380100000001
+623031300012744-5678-99      0000000000031300010000001Best. #1                1231380100000001
 70522*12200004*3*123987654321*777777777*DOE*JOHN*1\                                00010000001
-82250000020003130001000000000000000000000000231380104                          231380100000001
+82200000020003130001000000000000000000000000231380104                          231380100000001
 9000001000001000000020003130001000000000000000000000000                                       
 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/ach-enr-read/main.go b/test/ach-enr-read/main.go
index 8d85232f..bd0f5c68 100644
--- a/test/ach-enr-read/main.go
+++ b/test/ach-enr-read/main.go
@@ -55,7 +55,7 @@ func main() {
 	add := batch.GetEntries()[0].Addenda05[0]
 
 	fmt.Printf("Payment Related Information: %s\n", add.PaymentRelatedInformation)
-	info, err := batch.ParsePaymentInformation(add)
+	info, err := ach.ParseENRPaymentInformation(add)
 	if err != nil {
 		log.Fatalf("Problem Parsing ENR Addenda05 PaymentRelatedInformation: %v\n", err)
 	}
diff --git a/test/ach-enr-write/main.go b/test/ach-enr-write/main.go
index ba609fe0..d55b8bef 100644
--- a/test/ach-enr-write/main.go
+++ b/test/ach-enr-write/main.go
@@ -40,7 +40,7 @@ func main() {
 
 	// BatchHeader identifies the originating entity and the type of transactions contained in the batch
 	bh := ach.NewBatchHeader()
-	bh.ServiceClassCode = ach.DebitsOnly
+	bh.ServiceClassCode = ach.CreditsOnly
 	bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver
 	bh.CompanyIdentification = fh.ImmediateOrigin
 	bh.StandardEntryClassCode = ach.ENR
@@ -51,7 +51,7 @@ func main() {
 	// can be multiple entries per batch
 	entry := ach.NewEntryDetail()
 	// Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL)
-	entry.TransactionCode = ach.CheckingDebit
+	entry.TransactionCode = ach.CheckingPrenoteCredit
 	entry.SetRDFI("031300012")             // Receiver's bank transit routing number
 	entry.DFIAccountNumber = "744-5678-99" // Receiver's bank account number
 	entry.Amount = 0                       // Amount of transaction with no decimal. One dollar and eleven cents = 111