Skip to content

Commit 8c0ba87

Browse files
committed
fix: validate amounts in non-prenote entries
Fixes: #1168
1 parent a362401 commit 8c0ba87

24 files changed

+173
-0
lines changed

batch.go

+19
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,25 @@ func (batch *Batch) IsADV() bool {
10111011
return ok
10121012
}
10131013

1014+
func (batch *Batch) ValidAmountForCodes(entry *EntryDetail) error {
1015+
if entry != nil && entry.Addenda98 != nil {
1016+
// NOC entries will have a zero'd amount value
1017+
return nil
1018+
}
1019+
isPrenote := entry.isPrenote(entry.TransactionCode)
1020+
if isPrenote {
1021+
if entry.Amount == 0 {
1022+
return nil
1023+
}
1024+
return fieldError("Amount", ErrBatchAmountNonZero, entry.Amount)
1025+
} else {
1026+
if entry.Amount == 0 {
1027+
return fieldError("Amount", ErrBatchAmountZero, entry.Amount)
1028+
}
1029+
}
1030+
return nil
1031+
}
1032+
10141033
// ValidTranCodeForServiceClassCode validates a TransactionCode is valid for a ServiceClassCode
10151034
func (batch *Batch) ValidTranCodeForServiceClassCode(entry *EntryDetail) error {
10161035
// ADV should use ADVEntryDetail

batchACK.go

+4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ func (batch *BatchACK) Validate() error {
6060
default:
6161
return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode)
6262
}
63+
// Verify the Amount is valid for SEC code and TransactionCode
64+
if err := batch.ValidAmountForCodes(entry); err != nil {
65+
return err
66+
}
6367
// Verify the TransactionCode is valid for a ServiceClassCode
6468
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
6569
return err

batchARC.go

+4
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ func (batch *BatchARC) Validate() error {
7979
if entry.IdentificationNumber == "" {
8080
return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber)
8181
}
82+
// Verify the Amount is valid for SEC code and TransactionCode
83+
if err := batch.ValidAmountForCodes(entry); err != nil {
84+
return err
85+
}
8286
// Verify the TransactionCode is valid for a ServiceClassCode
8387
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
8488
return err

batchATX.go

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ func (batch *BatchATX) Validate() error {
7777
if len(entry.Addenda05) != addendaRecords {
7878
return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(len(entry.Addenda05), addendaRecords))
7979
}
80+
// Verify the Amount is valid for SEC code and TransactionCode
81+
if err := batch.ValidAmountForCodes(entry); err != nil {
82+
return err
83+
}
8084
// Verify the TransactionCode is valid for a ServiceClassCode
8185
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
8286
return err

batchBOC.go

+4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ func (batch *BatchBOC) Validate() error {
8484
if entry.IdentificationNumber == "" {
8585
return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber)
8686
}
87+
// Verify the Amount is valid for SEC code and TransactionCode
88+
if err := batch.ValidAmountForCodes(entry); err != nil {
89+
return err
90+
}
8791
// Verify the TransactionCode is valid for a ServiceClassCode
8892
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
8993
return err

batchCCD.go

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ func (batch *BatchCCD) Validate() error {
5050
if len(entry.Addenda05) > 1 {
5151
return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
5252
}
53+
// Verify the Amount is valid for SEC code and TransactionCode
54+
if err := batch.ValidAmountForCodes(entry); err != nil {
55+
return err
56+
}
5357
// Verify the TransactionCode is valid for a ServiceClassCode
5458
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
5559
return err

batchCIE.go

+4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ func (batch *BatchCIE) Validate() error {
7171
if len(entry.Addenda05) > 1 {
7272
return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
7373
}
74+
// Verify the Amount is valid for SEC code and TransactionCode
75+
if err := batch.ValidAmountForCodes(entry); err != nil {
76+
return err
77+
}
7478
// Verify the TransactionCode is valid for a ServiceClassCode
7579
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
7680
return err

batchCOR.go

+4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ func (batch *BatchCOR) Validate() error {
7575
LoanZeroDollarRemittanceCredit:
7676
return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode)
7777
}
78+
// Verify the Amount is valid for SEC code and TransactionCode
79+
if err := batch.ValidAmountForCodes(entry); err != nil {
80+
return err
81+
}
7882
// Verify the TransactionCode is valid for a ServiceClassCode
7983
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
8084
return err

batchCTX.go

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ func (batch *BatchCTX) Validate() error {
7777
return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode)
7878
}
7979
}
80+
// Verify the Amount is valid for SEC code and TransactionCode
81+
if err := batch.ValidAmountForCodes(entry); err != nil {
82+
return err
83+
}
8084
// Verify the TransactionCode is valid for a ServiceClassCode
8185
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
8286
return err

batchDNE.go

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ func (batch *BatchDNE) Validate() error {
6969
if len(entry.Addenda05) != 1 {
7070
return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
7171
}
72+
// Verify the Amount is valid for SEC code and TransactionCode
73+
if err := batch.ValidAmountForCodes(entry); err != nil {
74+
return err
75+
}
7276
// Verify the TransactionCode is valid for a ServiceClassCode
7377
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
7478
return err

batchENR.go

+5
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,14 @@ func (batch *BatchENR) Validate() error {
6666

6767
switch entry.TransactionCode {
6868
case CheckingCredit, CheckingDebit, SavingsCredit, SavingsDebit:
69+
// nothing
6970
default:
7071
return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode)
7172
}
73+
// Verify the Amount is valid for SEC code and TransactionCode
74+
if err := batch.ValidAmountForCodes(entry); err != nil {
75+
return err
76+
}
7277
// Verify the TransactionCode is valid for a ServiceClassCode
7378
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
7479
return err

batchMTE.go

+4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ func (batch *BatchMTE) Validate() error {
5858
if entry.Amount <= 0 {
5959
return batch.Error("Amount", ErrBatchAmountZero, entry.Amount)
6060
}
61+
// Verify the Amount is valid for SEC code and TransactionCode
62+
if err := batch.ValidAmountForCodes(entry); err != nil {
63+
return err
64+
}
6165
// Verify the TransactionCode is valid for a ServiceClassCode
6266
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
6367
return err

batchPOP.go

+4
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ func (batch *BatchPOP) Validate() error {
7979
if entry.IdentificationNumber == "" {
8080
return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber)
8181
}
82+
// Verify the Amount is valid for SEC code and TransactionCode
83+
if err := batch.ValidAmountForCodes(entry); err != nil {
84+
return err
85+
}
8286
// Verify the TransactionCode is valid for a ServiceClassCode
8387
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
8488
return err

batchPOS.go

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ func (batch *BatchPOS) Validate() error {
6868
if err := entry.isCardTransactionType(entry.DiscretionaryData); err != nil {
6969
return batch.Error("CardTransactionType", ErrBatchInvalidCardTransactionType, entry.DiscretionaryData)
7070
}
71+
// Verify the Amount is valid for SEC code and TransactionCode
72+
if err := batch.ValidAmountForCodes(entry); err != nil {
73+
return err
74+
}
7175
// Verify the TransactionCode is valid for a ServiceClassCode
7276
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
7377
return err

batchPPD.go

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ func (batch *BatchPPD) Validate() error {
5151
if len(entry.Addenda05) > 1 {
5252
return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
5353
}
54+
// Verify the Amount is valid for SEC code and TransactionCode
55+
if err := batch.ValidAmountForCodes(entry); err != nil {
56+
return err
57+
}
5458
// Verify the TransactionCode is valid for a ServiceClassCode
5559
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
5660
return err

batchRCK.go

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ func (batch *BatchRCK) Validate() error {
7373
if entry.IdentificationNumber == "" {
7474
return batch.Error("CheckSerialNumber", ErrBatchCheckSerialNumber)
7575
}
76+
// Verify the Amount is valid for SEC code and TransactionCode
77+
if err := batch.ValidAmountForCodes(entry); err != nil {
78+
return err
79+
}
7680
// Verify the TransactionCode is valid for a ServiceClassCode
7781
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
7882
return err

batchSHR.go

+4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ func (batch *BatchSHR) Validate() error {
8282
if err := entry.isCreditCardYear(year); err != nil {
8383
return fieldError("CardExpirationDate", ErrValidYear, year)
8484
}
85+
// Verify the Amount is valid for SEC code and TransactionCode
86+
if err := batch.ValidAmountForCodes(entry); err != nil {
87+
return err
88+
}
8589
// Verify the TransactionCode is valid for a ServiceClassCode
8690
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
8791
return err

batchTEL.go

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ func (batch *BatchTEL) Validate() error {
5050
if entry.CreditOrDebit() != "D" {
5151
return batch.Error("TransactionCode", ErrBatchDebitOnly, entry.TransactionCode)
5252
}
53+
// Verify the Amount is valid for SEC code and TransactionCode
54+
if err := batch.ValidAmountForCodes(entry); err != nil {
55+
return err
56+
}
5357
// Verify the TransactionCode is valid for a ServiceClassCode
5458
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
5559
return err

batchTRC.go

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func (batch *BatchTRC) Validate() error {
6767
if entry.ItemResearchNumber() == "" {
6868
return batch.Error("ItemResearchNumber", ErrFieldRequired)
6969
}
70+
// Verify the Amount is valid for SEC code and TransactionCode
71+
if err := batch.ValidAmountForCodes(entry); err != nil {
72+
return err
73+
}
7074
// Verify the TransactionCode is valid for a ServiceClassCode
7175
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
7276
return err

batchTRX.go

+4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ func (batch *BatchTRX) Validate() error {
7373
if len(entry.Addenda05) != addendaRecords {
7474
return batch.Error("AddendaCount", NewErrBatchExpectedAddendaCount(len(entry.Addenda05), addendaRecords))
7575
}
76+
// Verify the Amount is valid for SEC code and TransactionCode
77+
if err := batch.ValidAmountForCodes(entry); err != nil {
78+
return err
79+
}
7680
// Verify the TransactionCode is valid for a ServiceClassCode
7781
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
7882
return err

batchWeb.go

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func (batch *BatchWEB) Validate() error {
4949
if len(entry.Addenda05) > 1 {
5050
return batch.Error("AddendaCount", NewErrBatchAddendaCount(len(entry.Addenda05), 1))
5151
}
52+
// Verify the Amount is valid for SEC code and TransactionCode
53+
if err := batch.ValidAmountForCodes(entry); err != nil {
54+
return err
55+
}
5256
// Verify the TransactionCode is valid for a ServiceClassCode
5357
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
5458
return err

batchXCK.go

+4
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ func (batch *BatchXCK) Validate() error {
7171
if entry.ItemResearchNumber() == "" {
7272
return batch.Error("ItemResearchNumber", ErrFieldRequired)
7373
}
74+
// Verify the Amount is valid for SEC code and TransactionCode
75+
if err := batch.ValidAmountForCodes(entry); err != nil {
76+
return err
77+
}
7478
// Verify the TransactionCode is valid for a ServiceClassCode
7579
if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil {
7680
return err

test/issues/issue1168_test.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to The Moov Authors under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. The Moov Authors licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package issues
19+
20+
import (
21+
"testing"
22+
"time"
23+
24+
"github.com/moov-io/ach"
25+
26+
"github.com/stretchr/testify/require"
27+
)
28+
29+
func TestIssue1168(t *testing.T) {
30+
// Batch Header
31+
bh := ach.NewBatchHeader()
32+
bh.ServiceClassCode = 200
33+
bh.CompanyName = "COMPANY"
34+
bh.CompanyIdentification = "IDENTIFICATION"
35+
bh.StandardEntryClassCode = ach.WEB
36+
bh.CompanyEntryDescription = "DESCRIPTION"
37+
bh.CompanyDescriptiveDate = time.Now().Format("060102")
38+
bh.EffectiveEntryDate = time.Now().Add(time.Hour * 24).Format("060102")
39+
bh.ODFIIdentification = "1"
40+
41+
ed := &ach.EntryDetail{
42+
TransactionCode: ach.SavingsCredit,
43+
DFIAccountNumber: "54321",
44+
RDFIIdentification: "12345678",
45+
CheckDigit: "0",
46+
Amount: 0,
47+
IndividualName: "Jane Doe",
48+
TraceNumber: "445566778899",
49+
}
50+
51+
b1 := ach.NewBatchWEB(bh)
52+
b1.AddEntry(ed)
53+
54+
// Require that Batches cannot have zero amounts
55+
require.ErrorContains(t, b1.Create(), ach.ErrBatchAmountZero.Error())
56+
57+
ed.TransactionCode = ach.SavingsPrenoteCredit
58+
require.NoError(t, b1.Create())
59+
}

validators.go

+10
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,16 @@ func StandardTransactionCode(code int) error {
389389
return ErrTransactionCode
390390
}
391391

392+
func (v *validator) isPrenote(code int) bool {
393+
switch code {
394+
case CheckingPrenoteCredit, CheckingPrenoteDebit,
395+
SavingsPrenoteCredit, SavingsPrenoteDebit,
396+
GLPrenoteCredit, GLPrenoteDebit, LoanPrenoteCredit:
397+
return true
398+
}
399+
return false
400+
}
401+
392402
// isTransactionTypeCode verifies Addenda10 TransactionTypeCode is a valid value
393403
// ANN = Annuity, BUS = Business/Commercial, DEP = Deposit, LOA = Loan, MIS = Miscellaneous, MOR = Mortgage
394404
// PEN = Pension, RLS = Rent/Lease, REM = Remittance2, SAL = Salary/Payroll, TAX = Tax, TEL = Telephone-Initiated Transaction

0 commit comments

Comments
 (0)