From 25fec5192a0137c6d9d6b311e7b57af2a8b66124 Mon Sep 17 00:00:00 2001 From: Henry Shih Date: Wed, 24 Jul 2024 17:10:01 -0700 Subject: [PATCH] feat: add AllowZeroEntryAmount validation option Fixes: https://github.com/moov-io/ach/issues/1450 --- batch.go | 3 +++ batch_test.go | 17 +++++++++++++++++ docs/custom-validation.md | 3 +++ file.go | 3 +++ server/validate.go | 4 ++++ 5 files changed, 30 insertions(+) diff --git a/batch.go b/batch.go index 5dc62eda7..160cfbea8 100644 --- a/batch.go +++ b/batch.go @@ -1064,6 +1064,9 @@ func (batch *Batch) ValidAmountForCodes(entry *EntryDetail) error { return fieldError("Amount", ErrBatchAmountNonZero, entry.Amount) } else { if entry.Amount == 0 { + if batch.validateOpts != nil && batch.validateOpts.AllowZeroEntryAmount { + return nil + } return fieldError("Amount", ErrBatchAmountZero, entry.Amount) } } diff --git a/batch_test.go b/batch_test.go index af80f750f..0272333e3 100644 --- a/batch_test.go +++ b/batch_test.go @@ -1121,6 +1121,23 @@ func TestBatch__ValidAmountForCodes(t *testing.T) { }) } +func TestBatch_ValidAmountForCodes_AllowZeroEntryAmount(t *testing.T) { + b1 := mockBatchWEB(t) + + // Standard EntryDetails should pass + require.NoError(t, b1.Create()) + + // Verify a zero amount fails + b1.Entries[0].Amount = 0 + require.ErrorContains(t, b1.Create(), ErrBatchAmountZero.Error()) + + // Bypass zero amount check + b1.SetValidation(&ValidateOpts{ + AllowZeroEntryAmount: true, + }) + require.NoError(t, b1.Create()) +} + func TestBatch_AllowInvalidAmounts(t *testing.T) { bh := &BatchHeader{ OriginatorStatusCode: 1, diff --git a/docs/custom-validation.md b/docs/custom-validation.md index df18fce84..010cfb09f 100644 --- a/docs/custom-validation.md +++ b/docs/custom-validation.md @@ -92,6 +92,9 @@ UnequalAddendaCounts bool `json:"unequalAddendaCounts"` ``` // AllowInvalidAmounts will skip verifying the Amount is valid for the TransactionCode and entry type. AllowInvalidAmounts bool `json:"allowInvalidAmounts"` + +// AllowZeroEntryAmount will skip enforcing the entry Amount to be non-zero. +AllowZeroEntryAmount bool `json:"allowZeroEntryAmount"` ``` ### File Header diff --git a/file.go b/file.go index 426c1f75a..600996f37 100644 --- a/file.go +++ b/file.go @@ -712,6 +712,9 @@ type ValidateOpts struct { // AllowInvalidAmounts will skip verifying the Amount is valid for the TransactionCode and entry type. AllowInvalidAmounts bool `json:"allowInvalidAmounts"` + + // AllowZeroEntryAmount will skip enforcing the entry Amount to be non-zero + AllowZeroEntryAmount bool `json:"allowZeroEntryAmount"` } // merge will combine two ValidateOpts structs and keep any non-zero field values. diff --git a/server/validate.go b/server/validate.go index 069f9b49a..b9c52785b 100644 --- a/server/validate.go +++ b/server/validate.go @@ -48,6 +48,7 @@ const ( unequalAddendaCounts = "unequalAddendaCounts" preserveSpaces = "preserveSpaces" allowInvalidAmounts = "allowInvalidAmounts" + allowZeroEntryAmount = "allowZeroEntryAmount" ) // readValidateOpts parses ValidateOpts from the URL query parameters and from the request body. @@ -75,6 +76,7 @@ func readValidateOpts(request *http.Request) (io.Reader, *ach.ValidateOpts, erro unequalAddendaCounts, preserveSpaces, allowInvalidAmounts, + allowZeroEntryAmount, } var buf bytes.Buffer @@ -130,6 +132,8 @@ func readValidateOpts(request *http.Request) (io.Reader, *ach.ValidateOpts, erro opts.PreserveSpaces = yes case allowInvalidAmounts: opts.AllowInvalidAmounts = yes + case allowZeroEntryAmount: + opts.AllowZeroEntryAmount = yes } }