Repository: moov-io/ach
Pull request #1002 (2022-03-28 15:04:22)

feat: add support for "Dishonored Returns" with Addenda04 records

codecov-commenter: # [Codecov](https://codecov.io/gh/moov-io/ach/pull/1002?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=moov-io) Report
> Merging [#1002](https://codecov.io/gh/moov-io/ach/pull/1002?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=moov-io) (b365561) into [master](https://codecov.io/gh/moov-io/ach/commit/1bbb082dcae25770751e850e4c81a3ebd165a3d0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=moov-io) (1bbb082) will **decrease** coverage by `0.33%`.
> The diff coverage is `75.67%`.


```diff
@@            Coverage Diff             @@
##           master    #1002      +/-   ##
==========================================
- Coverage   91.69%   91.35%   -0.34%     
==========================================
  Files          66       67       +1     
  Lines        6212     6317     +105     
==========================================
+ Hits         5696     5771      +75     
- Misses        317      334      +17     
- Partials      199      212      +13     
```

jawspeak: We discussed in slack and it was my error for getting this term "addenda04". This is an addenda with addenda type code of `99`, just for the sub-record set of fields in a dishonored return. This means Adam may be renaming it something else such as `addenda99_dishonored_return.go` or another better name. Addenda04 doesn't exist.

Patch:
diff --git a/addenda99_dishonored_return.go b/addenda99_dishonored_return.go
new file mode 100644
index 00000000..dde4608a
--- /dev/null
+++ b/addenda99_dishonored_return.go
@@ -0,0 +1,193 @@
+// 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 ach
+
+import (
+	"strings"
+	"unicode/utf8"
+)
+
+type Addenda99Dishonored struct {
+	// ID is a client defined string used as a reference to this record.
+	ID string `json:"id"`
+	// RecordType defines the type of record in the block. entryAddendaPos 7
+	recordType string
+	// TypeCode Addenda types code '99'
+	TypeCode string `json:"typeCode"`
+
+	// DishonoredReturnReasonCode is the return code explaining the dishonorment
+	DishonoredReturnReasonCode string
+
+	// OriginalEntryTraceNumber is the trace number specifieid in the initial entry
+	OriginalEntryTraceNumber string
+
+	// OriginalReceivingDFIIdentification is the DFI Identification specifieid in the initial entry
+	OriginalReceivingDFIIdentification string
+
+	// ReturnTraceNumber is the TraceNumber used when issuing the return
+	ReturnTraceNumber string
+
+	// ReturnSettlementDate is the date of return issuing
+	ReturnSettlementDate string
+
+	// ReturnReasonCode is the initial return code
+	ReturnReasonCode string
+
+	// AddendaInformation is additional data
+	AddendaInformation string
+
+	// TraceNumber is the trace number for dishonorment
+	TraceNumber string
+
+	// validator is composed for data validation
+	validator
+	// converters is composed for ACH to GoLang Converters
+	converters
+
+	validateOpts *ValidateOpts
+}
+
+// NewAddenda99Dishonored returns a new Addenda99Dishonored with default values for none exported fields
+func NewAddenda99Dishonored() *Addenda99Dishonored {
+	Addenda99Dishonored := &Addenda99Dishonored{
+		recordType: "7",
+		TypeCode:   "99",
+	}
+	return Addenda99Dishonored
+}
+
+func (Addenda99Dishonored *Addenda99Dishonored) Parse(record string) {
+	if utf8.RuneCountInString(record) != 94 {
+		return
+	}
+
+	Addenda99Dishonored.recordType = "7"
+	Addenda99Dishonored.TypeCode = record[1:3]
+	Addenda99Dishonored.DishonoredReturnReasonCode = record[3:6]
+	Addenda99Dishonored.OriginalEntryTraceNumber = record[6:21]
+	Addenda99Dishonored.OriginalReceivingDFIIdentification = record[27:35]
+	Addenda99Dishonored.ReturnTraceNumber = record[38:53]
+	Addenda99Dishonored.ReturnSettlementDate = record[53:56]
+	Addenda99Dishonored.ReturnReasonCode = record[56:58]
+	Addenda99Dishonored.AddendaInformation = record[58:79]
+	Addenda99Dishonored.TraceNumber = record[79:94]
+}
+
+func (Addenda99Dishonored *Addenda99Dishonored) String() string {
+	var buf strings.Builder
+	buf.Grow(94)
+	buf.WriteString(Addenda99Dishonored.recordType)
+	buf.WriteString(Addenda99Dishonored.TypeCode)
+	buf.WriteString(Addenda99Dishonored.DishonoredReturnReasonCodeField())
+	buf.WriteString(Addenda99Dishonored.OriginalEntryTraceNumberField())
+	buf.WriteString("      ")
+	buf.WriteString(Addenda99Dishonored.OriginalReceivingDFIIdentificationField())
+	buf.WriteString("   ")
+	buf.WriteString(Addenda99Dishonored.ReturnTraceNumberField())
+	buf.WriteString(Addenda99Dishonored.ReturnSettlementDateField())
+	buf.WriteString(Addenda99Dishonored.ReturnReasonCodeField())
+	buf.WriteString(Addenda99Dishonored.AddendaInformationField())
+	buf.WriteString(Addenda99Dishonored.TraceNumberField())
+	return buf.String()
+}
+
+// SetValidation stores ValidateOpts on the Batch which are to be used to override
+// the default NACHA validation rules.
+func (Addenda99Dishonored *Addenda99Dishonored) SetValidation(opts *ValidateOpts) {
+	if Addenda99Dishonored == nil {
+		return
+	}
+	Addenda99Dishonored.validateOpts = opts
+}
+
+func isODFIDishonoredReturn(code string) bool {
+	switch code {
+	case "R61", "R67", "R68", "R69", "R70": // Used by ODFI for Dishonored Returns
+		return true
+	}
+	return false
+}
+
+func isRDFIDishonoredReturn(code string) bool {
+	switch code {
+	case "R71", "R72", "R73", "R74", "R75", "R76": // Used by RDFI for Dishonored Returns
+		return true
+	}
+	return false
+}
+
+func IsDishonoredReturnCode(code string) bool {
+	return isODFIDishonoredReturn(code) || isRDFIDishonoredReturn(code)
+}
+
+// Validate verifies NACHA rules for Addenda99Dishonored
+func (Addenda99Dishonored *Addenda99Dishonored) Validate() error {
+	if Addenda99Dishonored.recordType != "7" {
+		return fieldError("recordType", NewErrRecordType(7), Addenda99Dishonored.recordType)
+	}
+	if Addenda99Dishonored.TypeCode == "" {
+		return fieldError("TypeCode", ErrConstructor, Addenda99Dishonored.TypeCode)
+	}
+	if Addenda99Dishonored.TypeCode != "99" {
+		return fieldError("TypeCode", ErrAddendaTypeCode, Addenda99Dishonored.TypeCode)
+	}
+
+	// Verify the DishonoredReturnReasonCode matches expected values
+	if Addenda99Dishonored.validateOpts == nil || !Addenda99Dishonored.validateOpts.CustomReturnCodes {
+		// We can validate the Dishonored ReturnCode
+		if !IsDishonoredReturnCode(Addenda99Dishonored.DishonoredReturnReasonCode) {
+			return fieldError("DishonoredReturnReasonCode", ErrAddenda99DishonoredReturnCode, Addenda99Dishonored.DishonoredReturnReasonCode)
+		}
+	}
+
+	return nil
+}
+
+func (Addenda99Dishonored *Addenda99Dishonored) DishonoredReturnReasonCodeField() string {
+	return Addenda99Dishonored.stringField(Addenda99Dishonored.DishonoredReturnReasonCode, 3)
+}
+
+// OriginalEntryTraceNumberField returns a zero padded TraceNumber string
+func (Addenda99Dishonored *Addenda99Dishonored) OriginalEntryTraceNumberField() string {
+	return Addenda99Dishonored.stringField(Addenda99Dishonored.OriginalEntryTraceNumber, 15)
+}
+
+func (Addenda99Dishonored *Addenda99Dishonored) OriginalReceivingDFIIdentificationField() string {
+	return Addenda99Dishonored.stringField(Addenda99Dishonored.OriginalReceivingDFIIdentification, 8)
+}
+
+func (Addenda99Dishonored *Addenda99Dishonored) ReturnTraceNumberField() string {
+	return Addenda99Dishonored.stringField(Addenda99Dishonored.ReturnTraceNumber, 15)
+}
+
+func (Addenda99Dishonored *Addenda99Dishonored) ReturnSettlementDateField() string {
+	return Addenda99Dishonored.stringField(Addenda99Dishonored.ReturnSettlementDate, 3)
+}
+
+func (Addenda99Dishonored *Addenda99Dishonored) ReturnReasonCodeField() string {
+	return Addenda99Dishonored.stringField(Addenda99Dishonored.ReturnReasonCode, 2)
+}
+
+func (Addenda99Dishonored *Addenda99Dishonored) AddendaInformationField() string {
+	return Addenda99Dishonored.alphaField(Addenda99Dishonored.AddendaInformation, 21)
+}
+
+// TraceNumberField returns a zero padded TraceNumber string
+func (Addenda99Dishonored *Addenda99Dishonored) TraceNumberField() string {
+	return Addenda99Dishonored.stringField(Addenda99Dishonored.TraceNumber, 15)
+}
diff --git a/batch.go b/batch.go
index 6d989e40..f9dd7faa 100644
--- a/batch.go
+++ b/batch.go
@@ -595,6 +595,11 @@ func (batch *Batch) isFieldInclusion() error {
 					return err
 				}
 			}
+			if entry.Addenda99Dishonored != nil {
+				if err := entry.Addenda99Dishonored.Validate(); err != nil {
+					return err
+				}
+			}
 
 		}
 		return batch.Control.Validate()
@@ -817,7 +822,6 @@ func (batch *Batch) isAddendaSequence() error {
 				return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator)
 			}
 		}
-
 		if len(entry.Addenda05) > 0 {
 			// addenda without indicator flag of 1
 			if entry.AddendaRecordIndicator != 1 {
@@ -848,6 +852,11 @@ func (batch *Batch) isAddendaSequence() error {
 				return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator)
 			}
 		}
+		if entry.Addenda99Dishonored != nil {
+			if entry.AddendaRecordIndicator != 1 {
+				return batch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator)
+			}
+		}
 	}
 	return nil
 }
@@ -889,7 +898,7 @@ func (batch *Batch) isCategory() error {
 // Notification of Change:
 // COR and Addenda98
 // Return:
-// Addenda99
+// Addenda99, Addenda99Dishonored
 //
 func (batch *Batch) addendaFieldInclusion(entry *EntryDetail) error {
 	switch entry.Category {
@@ -973,7 +982,7 @@ func (batch *Batch) addendaFieldInclusionReturn(entry *EntryDetail) error {
 	if entry.Addenda98 != nil {
 		return batch.Error("Addenda98", ErrBatchAddendaCategory, entry.Category)
 	}
-	if entry.Addenda99 == nil {
+	if entry.Addenda99 == nil && entry.Addenda99Dishonored == nil {
 		return batch.Error("Addenda99", ErrFieldInclusion)
 	}
 	return nil
diff --git a/cmd/achcli/describe/file.go b/cmd/achcli/describe/file.go
index 08e67469..63dd59f9 100644
--- a/cmd/achcli/describe/file.go
+++ b/cmd/achcli/describe/file.go
@@ -77,6 +77,7 @@ func File(ww io.Writer, file *ach.File, opts *Opts) {
 			}
 			dumpAddenda98(w, e.Addenda98)
 			dumpAddenda99(w, e.Addenda99)
+			dumpAddenda99Dishonored(w, e.Addenda99Dishonored)
 		}
 
 		bc := file.Batches[i].GetControl()
@@ -103,6 +104,18 @@ func dumpAddenda02(w *tabwriter.Writer, a *ach.Addenda02) {
 		a.TransactionDate, a.AuthorizationCodeOrExpireDate, a.TerminalLocation, a.TerminalCity, a.TerminalState, a.TraceNumber)
 }
 
+func dumpAddenda99Dishonored(w *tabwriter.Writer, a *ach.Addenda99Dishonored) {
+	if a == nil {
+		return
+	}
+
+	fmt.Fprintln(w, "\n      Dishonored Addenda99")
+	fmt.Fprintln(w, "      Dis. ReturnCode\tOrig. TraceNumber\tRDFI Identification\tTraceNumber\tSettlementDate\tReturnCode\tAddendaInformation\tTraceNumber")
+	fmt.Fprintf(w, "      %s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
+		a.DishonoredReturnReasonCode, a.OriginalEntryTraceNumber, a.OriginalReceivingDFIIdentification, a.ReturnTraceNumber,
+		a.ReturnSettlementDate, a.ReturnReasonCode, a.AddendaInformation, a.TraceNumber)
+}
+
 func dumpAddenda05(w *tabwriter.Writer, a *ach.Addenda05) {
 	if a == nil {
 		return
diff --git a/entryDetail.go b/entryDetail.go
index e33e08bf..68de349c 100644
--- a/entryDetail.go
+++ b/entryDetail.go
@@ -83,6 +83,8 @@ type EntryDetail struct {
 	Addenda98 *Addenda98 `json:"addenda98,omitempty"`
 	// Addenda99 for use with Returns
 	Addenda99 *Addenda99 `json:"addenda99,omitempty"`
+	// Addenda99Dishonored for use with Dishonored Returns
+	Addenda99Dishonored *Addenda99Dishonored `json:"addenda99Dishonored,omitempty"`
 	// Category defines if the entry is a Forward, Return, or NOC
 	Category string `json:"category,omitempty"`
 	// validator is composed for data validation
@@ -603,6 +605,9 @@ func (ed *EntryDetail) addendaCount() (n int) {
 	if ed.Addenda99 != nil {
 		n += 1
 	}
+	if ed.Addenda99Dishonored != nil {
+		n += 1
+	}
 	return n
 }
 
diff --git a/fieldErrors.go b/fieldErrors.go
index 8a5244d7..36ea87cb 100644
--- a/fieldErrors.go
+++ b/fieldErrors.go
@@ -76,6 +76,8 @@ var (
 	ErrAddenda98CorrectedData = errors.New("must contain the corrected information corresponding to the Change Code")
 	// ErrAddenda99ReturnCode is given when there's an invalid return code
 	ErrAddenda99ReturnCode = errors.New("found is not a valid return code")
+	// ErrAddenda99DishonoredReturnCode is given when there's an invalid dishonored return code
+	ErrAddenda99DishonoredReturnCode = errors.New("found is not a valid dishonored return code")
 	// ErrBatchCORAddenda is given when an entry in a COR batch does not have an addenda98
 	ErrBatchCORAddenda = errors.New("one Addenda98 record is required for each entry in SEC Type COR")
 
diff --git a/file.go b/file.go
index 754909c9..90cea478 100644
--- a/file.go
+++ b/file.go
@@ -256,6 +256,10 @@ func setEntryRecordType(e *EntryDetail) {
 		e.Addenda99.recordType = "7"
 		e.Addenda99.TypeCode = "99"
 	}
+	if e.Addenda99Dishonored != nil {
+		e.Addenda99Dishonored.recordType = "7"
+		e.Addenda99Dishonored.TypeCode = "99"
+	}
 }
 
 func setADVEntryRecordType(e *ADVEntryDetail) {
diff --git a/merge.go b/merge.go
index 9b23d599..d797a445 100644
--- a/merge.go
+++ b/merge.go
@@ -208,6 +208,9 @@ func lineCount(f *File) (int, error) {
 			if entries[j].Addenda99 != nil {
 				lines++
 			}
+			if entries[j].Addenda99Dishonored != nil {
+				lines++
+			}
 		}
 	}
 	for i := range f.IATBatches {
diff --git a/reader.go b/reader.go
index d651eac1..3ad9cd5c 100644
--- a/reader.go
+++ b/reader.go
@@ -457,14 +457,26 @@ func (r *Reader) parseAddenda() error {
 				r.currentBatch.GetEntries()[entryIndex].Category = CategoryNOC
 				r.currentBatch.GetEntries()[entryIndex].Addenda98 = addenda98
 			case "99":
-				addenda99 := NewAddenda99()
-				addenda99.Parse(r.line)
-				addenda99.SetValidation(r.File.validateOpts)
-				if err := addenda99.Validate(); err != nil {
-					return r.parseError(err)
+				// Addenda99 and Addenda99Dishonored records both have their code in the same spot,
+				// so we need to determine which to parse by the value.
+				if IsDishonoredReturnCode(r.line[3:6]) {
+					addenda99Dishonored := NewAddenda99Dishonored()
+					addenda99Dishonored.Parse(r.line)
+					addenda99Dishonored.SetValidation(r.File.validateOpts)
+					if err := addenda99Dishonored.Validate(); err != nil {
+						return r.parseError(err)
+					}
+					r.currentBatch.GetEntries()[entryIndex].Addenda99Dishonored = addenda99Dishonored
+				} else {
+					addenda99 := NewAddenda99()
+					addenda99.Parse(r.line)
+					addenda99.SetValidation(r.File.validateOpts)
+					if err := addenda99.Validate(); err != nil {
+						return r.parseError(err)
+					}
+					r.currentBatch.GetEntries()[entryIndex].Addenda99 = addenda99
 				}
 				r.currentBatch.GetEntries()[entryIndex].Category = CategoryReturn
-				r.currentBatch.GetEntries()[entryIndex].Addenda99 = addenda99
 			}
 		} else {
 			return r.parseError(r.currentBatch.Error("AddendaRecordIndicator", ErrBatchAddendaIndicator))
diff --git a/writer.go b/writer.go
index 3b35707f..58530aa6 100644
--- a/writer.go
+++ b/writer.go
@@ -127,6 +127,12 @@ func (w *Writer) writeBatch(file *File) error {
 					}
 					w.lineNum++
 				}
+				if entry.Addenda99Dishonored != nil {
+					if _, err := w.w.WriteString(entry.Addenda99Dishonored.String() + w.LineEnding); err != nil {
+						return err
+					}
+					w.lineNum++
+				}
 			}
 		} else {
 			for _, entry := range batch.GetADVEntries() {


Test patch:
diff --git a/addenda99_dishonored_return_test.go b/addenda99_dishonored_return_test.go
new file mode 100644
index 00000000..33189098
--- /dev/null
+++ b/addenda99_dishonored_return_test.go
@@ -0,0 +1,55 @@
+// 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 ach
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func mockAddenda99Dishonored() *Addenda99Dishonored {
+	addenda99 := NewAddenda99Dishonored()
+	addenda99.DishonoredReturnReasonCode = "R68"
+	addenda99.OriginalEntryTraceNumber = "059999990000301"
+	addenda99.OriginalReceivingDFIIdentification = "12391871"
+	addenda99.ReturnTraceNumber = "123918710000001"
+	addenda99.ReturnSettlementDate = "179"
+	addenda99.ReturnReasonCode = "01"
+	addenda99.AddendaInformation = "Untimely Return"
+	addenda99.TraceNumber = "059999990000001"
+	return addenda99
+}
+
+func TestAddenda99Dishonored__Fields(t *testing.T) {
+	addenda99 := mockAddenda99Dishonored()
+
+	// shorten some fields
+	addenda99.OriginalEntryTraceNumber = "0599999900301"
+	addenda99.ReturnTraceNumber = "123918710001"
+	addenda99.TraceNumber = "05999900001"
+
+	require.Equal(t, "R68", addenda99.DishonoredReturnReasonCodeField())
+	require.Equal(t, "000599999900301", addenda99.OriginalEntryTraceNumberField())
+	require.Equal(t, "12391871", addenda99.OriginalReceivingDFIIdentificationField())
+	require.Equal(t, "000123918710001", addenda99.ReturnTraceNumberField())
+	require.Equal(t, "179", addenda99.ReturnSettlementDateField())
+	require.Equal(t, "01", addenda99.ReturnReasonCodeField())
+	require.Equal(t, "Untimely Return      ", addenda99.AddendaInformationField())
+	require.Equal(t, "000005999900001", addenda99.TraceNumberField())
+}
diff --git a/batch_test.go b/batch_test.go
index 9111cb8b..b513c75d 100644
--- a/batch_test.go
+++ b/batch_test.go
@@ -20,6 +20,7 @@ package ach
 import (
 	"bytes"
 	"encoding/json"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -856,10 +857,8 @@ func TestBatchDishonoredReturnsCategory(t *testing.T) {
 	entry.AddendaRecordIndicator = 1
 	entry.Category = CategoryDishonoredReturn
 
-	addenda99 := mockAddenda99()
-	addenda99.ReturnCode = "R68"
-	addenda99.AddendaInformation = "Untimely Return"
-	entry.Addenda99 = addenda99
+	addenda99Dishonored := mockAddenda99Dishonored()
+	entry.Addenda99Dishonored = addenda99Dishonored
 
 	entryOne := NewEntryDetail()
 	entryOne.TransactionCode = CheckingDebit
@@ -873,10 +872,8 @@ func TestBatchDishonoredReturnsCategory(t *testing.T) {
 	entryOne.AddendaRecordIndicator = 1
 	entryOne.Category = CategoryReturn
 
-	addenda99One := mockAddenda99()
-	addenda99One.ReturnCode = "R68"
-	addenda99One.AddendaInformation = "Untimely Return"
-	entryOne.Addenda99 = addenda99One
+	addenda99DishonoredOne := mockAddenda99Dishonored()
+	entryOne.Addenda99Dishonored = addenda99DishonoredOne
 
 	posHeader := NewBatchHeader()
 	posHeader.ServiceClassCode = DebitsOnly
@@ -895,6 +892,29 @@ func TestBatchDishonoredReturnsCategory(t *testing.T) {
 	if !base.Match(err, NewErrBatchCategory("Return", "DishonoredReturn")) {
 		t.Errorf("%T: %s", err, err)
 	}
+
+	// Fix the entry after ensuring validation works
+	entryOne.Category = CategoryDishonoredReturn
+
+	file := NewFile()
+	file.SetHeader(mockFileHeader())
+	file.Control = mockFileControl()
+	file.AddBatch(batch)
+
+	if err := file.Create(); err != nil {
+		t.Fatal(err)
+	}
+
+	var buf bytes.Buffer
+	if err := NewWriter(&buf).Write(file); err != nil {
+		t.Fatal(err)
+	}
+
+	path := filepath.Join("examples", "testdata", "dishonored-return.ach")
+	err = ioutil.WriteFile(path, buf.Bytes(), 0600)
+	if err != nil {
+		t.Fatal(err)
+	}
 }
 
 // TestBatchConvertBatchType validates ConvertBatchType
diff --git a/examples/testdata/dishonored-return.ach b/examples/testdata/dishonored-return.ach
new file mode 100644
index 00000000..821b293c
--- /dev/null
+++ b/examples/testdata/dishonored-return.ach
@@ -0,0 +1,10 @@
+101 231380104 1210428822203301802A094101Federal Reserve Bank   My Bank Name                   
+5225Payee Name                          231380104 POSACH POS         000000   1231380100000001
+627121042882744-5678-99      000002500045689033       Wade Arnold           011231380100000001
+799R68059999990000301      12391871   12391871000000117901Untimely Return      059999990000001
+627121042882744-5678-99      000002300045689033       Adam Decaf            011231380100000002
+799R68059999990000301      12391871   12391871000000117901Untimely Return      059999990000001
+82250000040024208576000000048000000000000000231380104                          231380100000001
+9000001000001000000040024208576000000048000000000000000                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/writer_test.go b/writer_test.go
index 79e50f11..adbd9e3f 100644
--- a/writer_test.go
+++ b/writer_test.go
@@ -557,10 +557,8 @@ func TestPOSDishonoredReturnWrite(t *testing.T) {
 	entry.AddendaRecordIndicator = 1
 	entry.Category = CategoryDishonoredReturn
 
-	addenda99 := mockAddenda99()
-	addenda99.ReturnCode = "R68"
-	addenda99.AddendaInformation = "Untimely Return"
-	entry.Addenda99 = addenda99
+	addenda99Dishonored := mockAddenda99Dishonored()
+	entry.Addenda99Dishonored = addenda99Dishonored
 
 	posHeader := NewBatchHeader()
 	posHeader.ServiceClassCode = DebitsOnly