Repository: moov-io/ach
Pull request #1169 (2023-02-10 21:17:56)

fix: validate amounts in non-prenote entries
Fixes: https://github.com/moov-io/ach/issues/1168


Patch:
diff --git a/batch.go b/batch.go
index 8c7de391..16ab3536 100644
--- a/batch.go
+++ b/batch.go
@@ -1011,6 +1011,28 @@ func (batch *Batch) IsADV() bool {
 	return ok
 }
 
+func (batch *Batch) ValidAmountForCodes(entry *EntryDetail) error {
+	if entry != nil && entry.Addenda98 != nil {
+		// NOC entries will have a zero'd amount value
+		if entry.Amount != 0 {
+			return ErrBatchAmountNonZero
+		}
+		return nil
+	}
+	isPrenote := entry.isPrenote(entry.TransactionCode)
+	if isPrenote {
+		if entry.Amount == 0 {
+			return nil
+		}
+		return fieldError("Amount", ErrBatchAmountNonZero, entry.Amount)
+	} else {
+		if entry.Amount == 0 {
+			return fieldError("Amount", ErrBatchAmountZero, entry.Amount)
+		}
+	}
+	return nil
+}
+
 // ValidTranCodeForServiceClassCode validates a TransactionCode is valid for a ServiceClassCode
 func (batch *Batch) ValidTranCodeForServiceClassCode(entry *EntryDetail) error {
 	// ADV should use ADVEntryDetail
diff --git a/batchACK.go b/batchACK.go
index df2f0fec..52ec7d3f 100644
--- a/batchACK.go
+++ b/batchACK.go
@@ -60,6 +60,10 @@ func (batch *BatchACK) Validate() error {
 		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):
+		// 	return err
+		// }
 		// Verify the TransactionCode is valid for a ServiceClassCode
 		if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
 			return err
diff --git a/batchARC.go b/batchARC.go
index 5372973b..6587a4a3 100644
--- a/batchARC.go
+++ b/batchARC.go
@@ -79,6 +79,10 @@ func (batch *BatchARC) Validate() error {
 		if entry.IdentificationNumber == "" {
 			return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber)
 		}
+		// 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
diff --git a/batchATX.go b/batchATX.go
index 8e1cca16..8d04ab11 100644
--- a/batchATX.go
+++ b/batchATX.go
@@ -77,6 +77,10 @@ func (batch *BatchATX) Validate() error {
 		if len(entry.Addenda05) != addendaRecords {
 			return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(len(entry.Addenda05), addendaRecords))
 		}
+		// // Verify the Amount is valid for SEC code and TransactionCode
+		// if err := batch.ValidAmountForCodes(entry); err != nil { // TODO(adam):
+		// 	return err
+		// }
 		// Verify the TransactionCode is valid for a ServiceClassCode
 		if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
 			return err
diff --git a/batchBOC.go b/batchBOC.go
index 4d7a55ff..395b851c 100644
--- a/batchBOC.go
+++ b/batchBOC.go
@@ -84,6 +84,10 @@ func (batch *BatchBOC) Validate() error {
 		if entry.IdentificationNumber == "" {
 			return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber)
 		}
+		// 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
diff --git a/batchCCD.go b/batchCCD.go
index 52a7e646..9156a449 100644
--- a/batchCCD.go
+++ b/batchCCD.go
@@ -50,6 +50,10 @@ func (batch *BatchCCD) Validate() error {
 		if len(entry.Addenda05) > 1 {
 			return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
 		}
+		// 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
diff --git a/batchCIE.go b/batchCIE.go
index 6ac52ac1..0e0c92bd 100644
--- a/batchCIE.go
+++ b/batchCIE.go
@@ -71,6 +71,10 @@ func (batch *BatchCIE) Validate() error {
 		if len(entry.Addenda05) > 1 {
 			return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
 		}
+		// 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
diff --git a/batchCOR.go b/batchCOR.go
index fa98ffe8..3e26915c 100644
--- a/batchCOR.go
+++ b/batchCOR.go
@@ -75,6 +75,10 @@ func (batch *BatchCOR) Validate() error {
 			LoanZeroDollarRemittanceCredit:
 			return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode)
 		}
+		// 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
diff --git a/batchCTX.go b/batchCTX.go
index 7da49b3a..b502dabf 100644
--- a/batchCTX.go
+++ b/batchCTX.go
@@ -77,6 +77,10 @@ func (batch *BatchCTX) Validate() error {
 				return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode)
 			}
 		}
+		// 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
diff --git a/batchDNE.go b/batchDNE.go
index d8c55b6d..6d3bf49d 100644
--- a/batchDNE.go
+++ b/batchDNE.go
@@ -69,6 +69,10 @@ func (batch *BatchDNE) Validate() error {
 		if len(entry.Addenda05) != 1 {
 			return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
 		}
+		// // Verify the Amount is valid for SEC code and TransactionCode
+		// if err := batch.ValidAmountForCodes(entry); err != nil { // TODO(adam):
+		// 	return err
+		// }
 		// Verify the TransactionCode is valid for a ServiceClassCode
 		if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
 			return err
diff --git a/batchENR.go b/batchENR.go
index c51d9a62..a8e5dd0a 100644
--- a/batchENR.go
+++ b/batchENR.go
@@ -66,9 +66,14 @@ func (batch *BatchENR) Validate() error {
 
 		switch entry.TransactionCode {
 		case CheckingCredit, CheckingDebit, SavingsCredit, SavingsDebit:
+			// 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):
+		// 	return err
+		// }
 		// Verify the TransactionCode is valid for a ServiceClassCode
 		if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
 			return err
diff --git a/batchMTE.go b/batchMTE.go
index 77b14a11..c7c5f0f7 100644
--- a/batchMTE.go
+++ b/batchMTE.go
@@ -58,6 +58,10 @@ func (batch *BatchMTE) Validate() error {
 		if entry.Amount <= 0 {
 			return batch.Error("Amount", ErrBatchAmountZero, entry.Amount)
 		}
+		// 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
diff --git a/batchPOP.go b/batchPOP.go
index a007a339..a47f6563 100644
--- a/batchPOP.go
+++ b/batchPOP.go
@@ -79,6 +79,10 @@ func (batch *BatchPOP) Validate() error {
 		if entry.IdentificationNumber == "" {
 			return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber)
 		}
+		// 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
diff --git a/batchPOS.go b/batchPOS.go
index 20d0ec5c..4cbe34f0 100644
--- a/batchPOS.go
+++ b/batchPOS.go
@@ -68,6 +68,10 @@ func (batch *BatchPOS) Validate() error {
 		if err := entry.isCardTransactionType(entry.DiscretionaryData); err != nil {
 			return batch.Error("CardTransactionType", ErrBatchInvalidCardTransactionType, entry.DiscretionaryData)
 		}
+		// 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
diff --git a/batchPPD.go b/batchPPD.go
index 9fca6c36..d79fe044 100644
--- a/batchPPD.go
+++ b/batchPPD.go
@@ -51,6 +51,10 @@ func (batch *BatchPPD) Validate() error {
 		if len(entry.Addenda05) > 1 {
 			return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
 		}
+		// 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
diff --git a/batchRCK.go b/batchRCK.go
index 6df02895..f868bea1 100644
--- a/batchRCK.go
+++ b/batchRCK.go
@@ -73,6 +73,10 @@ func (batch *BatchRCK) Validate() error {
 		if entry.IdentificationNumber == "" {
 			return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber)
 		}
+		// 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
diff --git a/batchSHR.go b/batchSHR.go
index 63660d0c..43bf020a 100644
--- a/batchSHR.go
+++ b/batchSHR.go
@@ -82,6 +82,10 @@ func (batch *BatchSHR) Validate() error {
 		if err := entry.isCreditCardYear(year); err != nil {
 			return fieldError("CardExpirationDate", ErrValidYear, year)
 		}
+		// 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
diff --git a/batchTEL.go b/batchTEL.go
index 7fd1fc27..5d98000d 100644
--- a/batchTEL.go
+++ b/batchTEL.go
@@ -50,6 +50,10 @@ func (batch *BatchTEL) Validate() error {
 		if entry.CreditOrDebit() != "D" {
 			return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode)
 		}
+		// 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
diff --git a/batchTRC.go b/batchTRC.go
index 2e709784..4736fdee 100644
--- a/batchTRC.go
+++ b/batchTRC.go
@@ -67,6 +67,10 @@ func (batch *BatchTRC) Validate() error {
 		if entry.ItemResearchNumber() == "" {
 			return batch.Error("ItemResearchNumber", ErrFieldRequired)
 		}
+		// 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
diff --git a/batchTRX.go b/batchTRX.go
index d8014996..c7e64500 100644
--- a/batchTRX.go
+++ b/batchTRX.go
@@ -73,6 +73,10 @@ func (batch *BatchTRX) Validate() error {
 		if len(entry.Addenda05) != addendaRecords {
 			return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(len(entry.Addenda05), addendaRecords))
 		}
+		// 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
diff --git a/batchWeb.go b/batchWeb.go
index 8413876e..2006f604 100644
--- a/batchWeb.go
+++ b/batchWeb.go
@@ -49,6 +49,10 @@ func (batch *BatchWEB) Validate() error {
 		if len(entry.Addenda05) > 1 {
 			return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
 		}
+		// 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
diff --git a/batchXCK.go b/batchXCK.go
index b0be396f..616dee10 100644
--- a/batchXCK.go
+++ b/batchXCK.go
@@ -71,6 +71,10 @@ func (batch *BatchXCK) Validate() error {
 		if entry.ItemResearchNumber() == "" {
 			return batch.Error("ItemResearchNumber", ErrFieldRequired)
 		}
+		// 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
diff --git a/go.mod b/go.mod
index 5cfea849..7c0a35d9 100644
--- a/go.mod
+++ b/go.mod
@@ -30,7 +30,7 @@ require (
 	github.com/prometheus/common v0.38.0 // indirect
 	github.com/prometheus/procfs v0.8.0 // indirect
 	github.com/rickar/cal/v2 v2.1.10 // indirect
-	golang.org/x/sys v0.4.0 // indirect
+	golang.org/x/sys v0.5.0 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index cca73109..091e0c5a 100644
--- a/go.sum
+++ b/go.sum
@@ -71,8 +71,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/validators.go b/validators.go
index 8f1a6803..b965f30d 100644
--- a/validators.go
+++ b/validators.go
@@ -389,6 +389,16 @@ func StandardTransactionCode(code int) error {
 	return ErrTransactionCode
 }
 
+func (v *validator) isPrenote(code int) bool {
+	switch code {
+	case CheckingPrenoteCredit, CheckingPrenoteDebit,
+		SavingsPrenoteCredit, SavingsPrenoteDebit,
+		GLPrenoteCredit, GLPrenoteDebit, LoanPrenoteCredit:
+		return true
+	}
+	return false
+}
+
 // isTransactionTypeCode verifies Addenda10 TransactionTypeCode is a valid value
 // ANN = Annuity, BUS = Business/Commercial, DEP = Deposit, LOA = Loan, MIS = Miscellaneous, MOR = Mortgage
 // PEN = Pension, RLS = Rent/Lease, REM = Remittance2, SAL = Salary/Payroll, TAX = Tax, TEL = Telephone-Initiated Transaction


Test patch:
diff --git a/batchENR_test.go b/batchENR_test.go
index a2ef470d..3500b11c 100644
--- a/batchENR_test.go
+++ b/batchENR_test.go
@@ -253,7 +253,7 @@ func TestBatchENRAmount(t *testing.T) {
 // TestBatchENRTransactionCode validates TransactionCode
 func TestBatchENRTransactionCode(t *testing.T) {
 	mockBatch := mockBatchENR(t)
-	mockBatch.GetEntries()[0].TransactionCode = CheckingCredit
+	mockBatch.GetEntries()[0].TransactionCode = CheckingReturnNOCCredit
 	err := mockBatch.Create()
 	if !base.Match(err, ErrBatchTransactionCode) {
 		t.Errorf("%T: %s", err, err)
diff --git a/batchPOS_test.go b/batchPOS_test.go
index e2517711..2840eedd 100644
--- a/batchPOS_test.go
+++ b/batchPOS_test.go
@@ -343,7 +343,7 @@ func TestBatchPOSAddendum98(t *testing.T) {
 	mockBatch.GetEntries()[0].Category = CategoryNOC
 	mockBatch.GetEntries()[0].Addenda98 = mockAddenda98
 	err := mockBatch.Create()
-	if !base.Match(err, ErrFieldInclusion) {
+	if !base.Match(err, ErrBatchAmountNonZero) {
 		t.Errorf("%T: %s", err, err)
 	}
 }
diff --git a/batchWEB_test.go b/batchWEB_test.go
index 104134b6..9ec114c3 100644
--- a/batchWEB_test.go
+++ b/batchWEB_test.go
@@ -300,6 +300,7 @@ func TestBatchWEBCategoryReturnAddenda98(t *testing.T) {
 	mockBatch := NewBatchWEB(mockBatchWEBHeader())
 	mockBatch.AddEntry(mockWEBEntryDetail())
 	mockAddenda98 := mockAddenda98()
+	mockBatch.GetEntries()[0].Amount = 0
 	mockBatch.GetEntries()[0].Category = CategoryReturn
 	mockBatch.GetEntries()[0].AddendaRecordIndicator = 1
 	mockBatch.GetEntries()[0].Addenda98 = mockAddenda98
diff --git a/batch_test.go b/batch_test.go
index 667c5a0b..467e62c2 100644
--- a/batch_test.go
+++ b/batch_test.go
@@ -1036,6 +1036,30 @@ func TestBatchConvertBatchType(t *testing.T) {
 	}
 }
 
+func TestBatch__ValidAmountForCodes(t *testing.T) {
+	b1 := mockBatchWEB()
+
+	// Standard EntryDetails should pass
+	require.NoError(t, b1.Create())
+
+	// Pre-note entries must have zero'd amounts
+	require.Equal(t, 100_000_000, b1.Entries[0].Amount)
+	b1.Entries[0].TransactionCode = CheckingPrenoteCredit
+	require.ErrorContains(t, b1.Create(), ErrBatchAmountNonZero.Error())
+	b1.Entries[0].Amount = 0
+	require.NoError(t, b1.Create())
+
+	// NOC entries must have zero amounts
+	file, err := ReadFile(filepath.Join("test", "testdata", "cor-example.ach"))
+	require.NoError(t, err)
+	require.NoError(t, file.Validate())
+
+	batch, ok := file.Batches[0].(*BatchCOR)
+	require.True(t, ok)
+	batch.Entries[0].Amount = 12345
+	require.ErrorContains(t, batch.Create(), ErrBatchAmountNonZero.Error())
+}
+
 func TestBatch__Equal(t *testing.T) {
 	testFile := func(t *testing.T) *File {
 		t.Helper()
diff --git a/test/issues/issue1168_test.go b/test/issues/issue1168_test.go
new file mode 100644
index 00000000..b6276154
--- /dev/null
+++ b/test/issues/issue1168_test.go
@@ -0,0 +1,65 @@
+// Licensed to The Moov Authors under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. The Moov Authors licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package issues
+
+import (
+	"testing"
+	"time"
+
+	"github.com/moov-io/ach"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue1168(t *testing.T) {
+	// Batch Header
+	bh := ach.NewBatchHeader()
+	bh.ServiceClassCode = 200
+	bh.CompanyName = "COMPANY"
+	bh.CompanyIdentification = "IDENTIFICATION"
+	bh.StandardEntryClassCode = ach.WEB
+	bh.CompanyEntryDescription = "DESCRIPTION"
+	bh.CompanyDescriptiveDate = time.Now().Format("060102")
+	bh.EffectiveEntryDate = time.Now().Add(time.Hour * 24).Format("060102")
+	bh.ODFIIdentification = "1"
+
+	ed := &ach.EntryDetail{
+		TransactionCode:    ach.SavingsCredit,
+		DFIAccountNumber:   "54321",
+		RDFIIdentification: "12345678",
+		CheckDigit:         "0",
+		Amount:             0,
+		IndividualName:     "Jane Doe",
+		TraceNumber:        "445566778899",
+	}
+
+	b1 := ach.NewBatchWEB(bh)
+	b1.AddEntry(ed)
+
+	// Require that Batches cannot have zero amounts
+	require.ErrorContains(t, b1.Create(), ach.ErrBatchAmountZero.Error())
+
+	// Use a pre-note transaction code and verify success
+	ed.TransactionCode = ach.SavingsPrenoteCredit
+	require.NoError(t, b1.Create())
+	require.NoError(t, b1.Validate())
+
+	// Set a non-zero amount and catch error
+	ed.Amount = 1223
+	require.ErrorContains(t, b1.Create(), ach.ErrBatchAmountNonZero.Error())
+}