Repository: moov-io/ach
Pull request #231 (2018-07-30 19:25:27)

212 CTX Support
212 CTX Support


Patch:
diff --git a/README.md b/README.md
index ec6e0a10..021d859f 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ ACH is under active development but already in production for multiple companies
 	* CCD (Corporate credit or debit)
 	* CIE (Customer-Initiated Entry)
 	* COR (Automated Notification of Change(NOC))
+	* CTX (Corporate Trade Exchange)
 	* POP (Point of Purchase)
 	* POS (Point of Sale)
 	* PPD (Prearranged payment and deposits)
diff --git a/batch.go b/batch.go
index 274b2255..6e3b3bf5 100644
--- a/batch.go
+++ b/batch.go
@@ -37,10 +37,11 @@ func NewBatch(bh *BatchHeader) (Batcher, error) {
 		return NewBatchCIE(bh), nil
 	case "COR":
 		return NewBatchCOR(bh), nil
+	case "CTX":
+		return NewBatchCTX(bh), nil
 	case "IAT":
-		//ToDo: Update message to tell user to use iatBatch.go
-		msg := fmt.Sprintf(msgFileNoneSEC, bh.StandardEntryClassCode)
-		return nil, &FileError{FieldName: "StandardEntryClassCode", Msg: msg}
+		msg := fmt.Sprintf(msgFileIATSEC, bh.StandardEntryClassCode)
+		return nil, &FileError{FieldName: "StandardEntryClassCode", Value: bh.StandardEntryClassCode, Msg: msg}
 	case "POP":
 		return NewBatchPOP(bh), nil
 	case "POS":
@@ -58,7 +59,7 @@ func NewBatch(bh *BatchHeader) (Batcher, error) {
 	default:
 	}
 	msg := fmt.Sprintf(msgFileNoneSEC, bh.StandardEntryClassCode)
-	return nil, &FileError{FieldName: "StandardEntryClassCode", Msg: msg}
+	return nil, &FileError{FieldName: "StandardEntryClassCode", Value: bh.StandardEntryClassCode, Msg: msg}
 }
 
 // verify checks basic valid NACHA batch rules. Assumes properly parsed records. This does not mean it is a valid batch as validity is tied to each batch type
@@ -140,6 +141,7 @@ func (batch *batch) build() error {
 	seq := 1
 	for i, entry := range batch.Entries {
 		entryCount = entryCount + 1 + len(entry.Addendum)
+
 		currentTraceNumberODFI, err := strconv.Atoi(entry.TraceNumberField()[:8])
 		if err != nil {
 			return err
diff --git a/batchCOR.go b/batchCOR.go
index 3fc04e96..285b0037 100644
--- a/batchCOR.go
+++ b/batchCOR.go
@@ -53,14 +53,30 @@ func (batch *BatchCOR) Validate() error {
 	}
 
 	for _, entry := range batch.Entries {
-		// COR TransactionCode must be a Return or NOC transaction Code
-		// Return/NOC of a credit  21, 31, 41, 51
-		// Return/NOC of a debit 26, 36, 46, 56
-		if entry.TransactionCodeDescription() != ReturnOrNoc {
+		/* COR TransactionCode must be a Return or NOC transaction Code
+			   Return/NOC
+			   Credit:  21, 31, 41, 51
+			   Debit: 26, 36, 46, 56
+
+			   Automated payment/deposit
+			   Credit: 22, 32, 42, 52
+			   Debit: 27, 37, 47, 55 (reversal)
+
+			   Prenote
+			   Credit:  23, 33, 43, 53
+			   Debit: 28, 38, 48
+
+			   Zero dollar amount with remittance data
+			   Credit: 24, 34, 44, 54
+		 	   Debit: 29, 39, 49
+		*/
+		switch entry.TransactionCode {
+		case 22, 27, 32, 37, 42, 47, 52, 55,
+			23, 28, 33, 38, 43, 48, 53,
+			24, 29, 34, 39, 44, 49, 54:
 			msg := fmt.Sprintf(msgBatchTransactionCode, entry.TransactionCode, "COR")
 			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "TransactionCode", Msg: msg}
 		}
-
 	}
 
 	return nil
diff --git a/batchCTX.go b/batchCTX.go
new file mode 100644
index 00000000..c5f0eaa7
--- /dev/null
+++ b/batchCTX.go
@@ -0,0 +1,106 @@
+// Copyright 2018 The ACH Authors
+// Use of this source code is governed by an Apache License
+// license that can be found in the LICENSE file.
+
+package ach
+
+import (
+	"fmt"
+	"strconv"
+)
+
+// BatchCTX holds the BatchHeader and BatchControl and all EntryDetail for CTX Entries.
+//
+// The Corporate Trade Exchange (CTX) application provides the ability to collect and disburse
+// funds and information between companies. Generally it is used by businesses paying one another
+// for goods or services. These payments replace checks with an electronic process of debiting and
+// crediting invoices between the financial institutions of participating companies.
+type BatchCTX struct {
+	batch
+}
+
+var (
+	msgBatchCTXAddenda      = "9999 is the maximum addenda records for SEC code CTX"
+	msgBatchCTXAddendaCount = "%v entry detail addenda records not equal to addendum %v"
+	msgBatchCTXAddendaType  = "%T found where Addenda05 is required for SEC code CTX"
+)
+
+// NewBatchCTX returns a *BatchCTX
+func NewBatchCTX(bh *BatchHeader) *BatchCTX {
+	batch := new(BatchCTX)
+	batch.SetControl(NewBatchControl())
+	batch.SetHeader(bh)
+	return batch
+}
+
+// Validate checks valid NACHA batch rules. Assumes properly parsed records.
+func (batch *BatchCTX) Validate() error {
+	// basic verification of the batch before we validate specific rules.
+	if err := batch.verify(); err != nil {
+		return err
+	}
+	// Add configuration based validation for this type.
+
+	// Add type specific validation.
+
+	if batch.Header.StandardEntryClassCode != "CTX" {
+		msg := fmt.Sprintf(msgBatchSECType, batch.Header.StandardEntryClassCode, "CTX")
+		return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg}
+	}
+
+	for _, entry := range batch.Entries {
+
+		// Addenda validations - CTX Addenda must be Addenda05
+
+		// A maximum of 9999 addenda records for CTX entry details
+		if len(entry.Addendum) > 9999 {
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Addendum", Msg: msgBatchCTXAddenda}
+		}
+
+		// validate CTXAddendaRecord Field is equal to the actual number of Addenda records
+		// use 0 value if there is no Addenda records
+		addendaRecords, _ := strconv.Atoi(entry.CTXAddendaRecordsField())
+		if len(entry.Addendum) != addendaRecords {
+			msg := fmt.Sprintf(msgBatchCTXAddendaCount, addendaRecords, len(entry.Addendum))
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Addendum", Msg: msg}
+		}
+
+		if len(entry.Addendum) > 0 {
+
+			switch entry.TransactionCode {
+			// Prenote credit  23, 33, 43, 53
+			// Prenote debit 28, 38, 48
+			case 23, 28, 33, 38, 43, 48, 53:
+				msg := fmt.Sprintf(msgBatchTransactionCodeAddenda, entry.TransactionCode, "CTX")
+				return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Addendum", Msg: msg}
+			default:
+			}
+
+			for i := range entry.Addendum {
+				addenda05, ok := entry.Addendum[i].(*Addenda05)
+				if !ok {
+					msg := fmt.Sprintf(msgBatchCTXAddendaType, entry.Addendum[i])
+					return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Addendum", Msg: msg}
+				}
+				if err := addenda05.Validate(); err != nil {
+					// convert the field error in to a batch error for a consistent api
+					if e, ok := err.(*FieldError); ok {
+						return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: e.FieldName, Msg: e.Msg}
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// Create takes Batch Header and Entries and builds a valid batch
+func (batch *BatchCTX) Create() error {
+	// generates sequence numbers and batch control
+	if err := batch.build(); err != nil {
+		return err
+	}
+	// Additional steps specific to batch type
+	// ...
+	return batch.Validate()
+}
diff --git a/batcher.go b/batcher.go
index ca541b2d..ce8f6865 100644
--- a/batcher.go
+++ b/batcher.go
@@ -65,4 +65,5 @@ var (
 	msgBatchCheckSerialNumber       = "Check Serial Number is required for SEC code %v"
 	msgBatchTransactionCode         = "Transaction code %v is not allowed for batch type %v"
 	msgBatchCardTransactionType     = "Card Transaction Type %v is invalid"
+	msgBatchTransactionCodeAddenda  = "Addenda not allowed for transaction code %v for batch type %v"
 )
diff --git a/entryDetail.go b/entryDetail.go
index 0594650b..1c757fb5 100644
--- a/entryDetail.go
+++ b/entryDetail.go
@@ -84,7 +84,6 @@ const (
 	// CategoryNOC defines the entry as being a notification of change of a forward entry to the originating institution
 	CategoryNOC = "NOC"
 	// ReturnOrNoc is the description for the  following TransactionCode: 21, 31, 41, 51, 26, 36, 46, 56
-	ReturnOrNoc = "RN"
 )
 
 // NewEntryDetail returns a new EntryDetail with default values for non exported fields
@@ -300,21 +299,18 @@ func (ed *EntryDetail) SetPOPTerminalState(s string) {
 // POPCheckSerialNumberField is used in POP, characters 1-9 of underlying BatchPOP
 // CheckSerialNumber / IdentificationNumber
 func (ed *EntryDetail) POPCheckSerialNumberField() string {
-	//return ed.alphaField(ed.IdentificationNumber, 9)
 	return ed.parseStringField(ed.IdentificationNumber[0:9])
 }
 
 // POPTerminalCityField is used in POP, characters 10-13 of underlying BatchPOP
 // CheckSerialNumber / IdentificationNumber
 func (ed *EntryDetail) POPTerminalCityField() string {
-	//return ed.alphaField(ed.IdentificationNumber, 9)
 	return ed.parseStringField(ed.IdentificationNumber[9:13])
 }
 
 // POPTerminalStateField is used in POP, characters 14-15 of underlying BatchPOP
 // CheckSerialNumber / IdentificationNumber
 func (ed *EntryDetail) POPTerminalStateField() string {
-	//return ed.alphaField(ed.IdentificationNumber, 9)
 	return ed.parseStringField(ed.IdentificationNumber[13:15])
 }
 
@@ -369,6 +365,32 @@ func (ed *EntryDetail) SetReceivingCompany(s string) {
 	ed.IndividualName = s
 }
 
+// SetCTXAddendaRecords setter for CTX AddendaRecords characters 1-4 of underlying IndividualName
+func (ed *EntryDetail) SetCTXAddendaRecords(i int) {
+	ed.IndividualName = ed.numericField(i, 4)
+}
+
+// SetCTXReceivingCompany setter for CTX ReceivingCompany characters 5-20 underlying IndividualName
+// Position 21-22 of underlying Individual Name are reserved blank space for CTX "  "
+func (ed *EntryDetail) SetCTXReceivingCompany(s string) {
+	ed.IndividualName = ed.IndividualName + ed.alphaField(s, 16) + "  "
+}
+
+// CTXAddendaRecordsField is used in CTX files, characters 1-4 of underlying IndividualName field
+func (ed *EntryDetail) CTXAddendaRecordsField() string {
+	return ed.parseStringField(ed.IndividualName[0:4])
+}
+
+// CTXReceivingCompanyField is used in CTX files, characters 5-20 of underlying IndividualName field
+func (ed *EntryDetail) CTXReceivingCompanyField() string {
+	return ed.parseStringField(ed.IndividualName[4:20])
+}
+
+// CTXReservedField is used in CTX files, characters 21-22 of underlying IndividualName field
+func (ed *EntryDetail) CTXReservedField() string {
+	return ed.IndividualName[20:22]
+}
+
 // DiscretionaryDataField returns a space padded string of DiscretionaryData
 func (ed *EntryDetail) DiscretionaryDataField() string {
 	return ed.alphaField(ed.DiscretionaryData, 2)
@@ -409,17 +431,3 @@ func (ed *EntryDetail) CreditOrDebit() string {
 	}
 	return ""
 }
-
-// TransactionCodeDescription determines the transaction code description based on the second number
-// in the TransactionCode
-func (ed *EntryDetail) TransactionCodeDescription() string {
-	tc := strconv.Itoa(ed.TransactionCode)
-	// Take the second number in the TransactionCode
-	switch tc[1:2] {
-	// Return or NOC
-	case "1", "6":
-		return ReturnOrNoc
-	default:
-	}
-	return ""
-}
diff --git a/file.go b/file.go
index 4ddb0786..603b49d7 100644
--- a/file.go
+++ b/file.go
@@ -37,7 +37,8 @@ var (
 	msgFileControl       = "none or more than one file control exists"
 	msgFileHeader        = "none or more than one file headers exists"
 	msgUnknownRecordType = "%s is an unknown record type"
-	msgFileNoneSEC       = "%v SEC(standard entry class) is not implemented"
+	msgFileNoneSEC       = "%v Standard Entry Class Code is not implemented"
+	msgFileIATSEC        = "%v Standard Entry Class Code should use iatBatch"
 )
 
 // FileError is an error describing issues validating a file
diff --git a/iatBatch.go b/iatBatch.go
index 7b7723ed..22eccb13 100644
--- a/iatBatch.go
+++ b/iatBatch.go
@@ -41,8 +41,8 @@ type IATBatch struct {
 	converters
 }
 
-// NewIATBatch takes a BatchHeader and returns a matching SEC code batch type that is a batcher. Returns an error if the SEC code is not supported.
-func NewIATBatch(bh *IATBatchHeader) IATBatch {
+// IATNewBatch takes a BatchHeader and returns a matching SEC code batch type that is a batcher. Returns an error if the SEC code is not supported.
+func IATNewBatch(bh *IATBatchHeader) IATBatch {
 	iatBatch := IATBatch{}
 	iatBatch.SetControl(NewBatchControl())
 	iatBatch.SetHeader(bh)
diff --git a/iatEntryDetail.go b/iatEntryDetail.go
index 66ee7cdd..9255c2bb 100644
--- a/iatEntryDetail.go
+++ b/iatEntryDetail.go
@@ -49,7 +49,7 @@ type IATEntryDetail struct {
 	// SecondaryOFACSreeningIndicator - Leave blank
 	SecondaryOFACSreeningIndicator string `json:"SecondaryOFACSreeningIndicator"`
 	// AddendaRecordIndicator indicates the existence of an Addenda Record.
-	// A value of "1" indicates that one ore more addenda records follow,
+	// A value of "1" indicates that one or more addenda records follow,
 	// and "0" means no such record is present.
 	AddendaRecordIndicator int `json:"addendaRecordIndicator,omitempty"`
 	// TraceNumber assigned by the ODFI in ascending sequence, is included in each
diff --git a/reader.go b/reader.go
index 6e12b280..3af43a33 100644
--- a/reader.go
+++ b/reader.go
@@ -393,7 +393,7 @@ func (r *Reader) parseIATBatchHeader() error {
 	}
 
 	// Passing BatchHeader into NewBatchIAT creates a Batcher of IAT SEC code type.
-	iatBatch := NewIATBatch(bh)
+	iatBatch := IATNewBatch(bh)
 
 	r.addIATCurrentBatch(iatBatch)
 


Test patch:
diff --git a/addenda05_test.go b/addenda05_test.go
index 2b9cbd65..7e489c40 100644
--- a/addenda05_test.go
+++ b/addenda05_test.go
@@ -12,6 +12,7 @@ import (
 func mockAddenda05() *Addenda05 {
 	addenda05 := NewAddenda05()
 	addenda05.SequenceNumber = 1
+	addenda05.PaymentRelatedInformation = "This is an Addenda05"
 	addenda05.EntryDetailSequenceNumber = 0000001
 
 	return addenda05
diff --git a/batchCOR_test.go b/batchCOR_test.go
index 55cc226e..0b754401 100644
--- a/batchCOR_test.go
+++ b/batchCOR_test.go
@@ -5,6 +5,7 @@
 package ach
 
 import (
+	"log"
 	"testing"
 )
 
@@ -42,7 +43,7 @@ func mockBatchCOR() *BatchCOR {
 	mockBatch.AddEntry(mockCOREntryDetail())
 	mockBatch.GetEntries()[0].AddAddenda(mockAddenda98())
 	if err := mockBatch.Create(); err != nil {
-		panic(err)
+		log.Fatal(err)
 	}
 	return mockBatch
 }
diff --git a/batchCTX_test.go b/batchCTX_test.go
new file mode 100644
index 00000000..65957063
--- /dev/null
+++ b/batchCTX_test.go
@@ -0,0 +1,546 @@
+// Copyright 2018 The ACH Authors
+// Use of this source code is governed by an Apache License
+// license that can be found in the LICENSE file.
+
+package ach
+
+import (
+	"log"
+	"testing"
+)
+
+// mockBatchCTXHeader creates a BatchCTX BatchHeader
+func mockBatchCTXHeader() *BatchHeader {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 220
+	bh.StandardEntryClassCode = "CTX"
+	bh.CompanyName = "Payee Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "ACH CTX"
+	bh.ODFIIdentification = "12104288"
+	return bh
+}
+
+// mockCTXEntryDetail creates a BatchCTX EntryDetail
+func mockCTXEntryDetail() *EntryDetail {
+	entry := NewEntryDetail()
+	entry.TransactionCode = 22
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.IdentificationNumber = "45689033"
+	entry.SetCTXAddendaRecords(1)
+	entry.SetCTXReceivingCompany("Receiver Company")
+	entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1)
+	entry.DiscretionaryData = "01"
+	entry.Category = CategoryForward
+	return entry
+}
+
+// mockBatchCTX creates a BatchCTX
+func mockBatchCTX() *BatchCTX {
+	mockBatch := NewBatchCTX(mockBatchCTXHeader())
+	mockBatch.AddEntry(mockCTXEntryDetail())
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
+	if err := mockBatch.Create(); err != nil {
+		log.Fatal(err)
+	}
+	return mockBatch
+}
+
+// testBatchCTXHeader creates a BatchCTX BatchHeader
+func testBatchCTXHeader(t testing.TB) {
+	batch, _ := NewBatch(mockBatchCTXHeader())
+	err, ok := batch.(*BatchCTX)
+	if !ok {
+		t.Errorf("Expecting BatchCTX got %T", err)
+	}
+}
+
+// TestBatchCTXHeader tests validating BatchCTX BatchHeader
+func TestBatchCTXHeader(t *testing.T) {
+	testBatchCTXHeader(t)
+}
+
+// BenchmarkBatchCTXHeader benchmarks validating BatchCTX BatchHeader
+func BenchmarkBatchCTXHeader(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXHeader(b)
+	}
+}
+
+// testBatchCTXCreate validates BatchCTX create
+func testBatchCTXCreate(t testing.TB) {
+	mockBatch := mockBatchCTX()
+	if err := mockBatch.Validate(); err != nil {
+		t.Errorf("%T: %s", err, err)
+	}
+}
+
+// TestBatchCTXCreate tests validating BatchCTX create
+func TestBatchCTXCreate(t *testing.T) {
+	testBatchCTXCreate(t)
+}
+
+// BenchmarkBatchCTXCreate benchmarks validating BatchCTX create
+func BenchmarkBatchCTXCreate(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXCreate(b)
+	}
+}
+
+// testBatchCTXStandardEntryClassCode validates BatchCTX create for an invalid StandardEntryClassCode
+func testBatchCTXStandardEntryClassCode(t testing.TB) {
+	mockBatch := mockBatchCTX()
+	mockBatch.Header.StandardEntryClassCode = "WEB"
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "StandardEntryClassCode" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXStandardEntryClassCode tests validating BatchCTX create for an invalid StandardEntryClassCode
+func TestBatchCTXStandardEntryClassCode(t *testing.T) {
+	testBatchCTXStandardEntryClassCode(t)
+}
+
+// BenchmarkBatchCTXStandardEntryClassCode benchmarks validating BatchCTX create for an invalid StandardEntryClassCode
+func BenchmarkBatchCTXStandardEntryClassCode(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXStandardEntryClassCode(b)
+	}
+}
+
+// testBatchCTXServiceClassCodeEquality validates service class code equality
+func testBatchCTXServiceClassCodeEquality(t testing.TB) {
+	mockBatch := mockBatchCTX()
+	mockBatch.GetControl().ServiceClassCode = 200
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "ServiceClassCode" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXServiceClassCodeEquality tests validating service class code equality
+func TestBatchCTXServiceClassCodeEquality(t *testing.T) {
+	testBatchCTXServiceClassCodeEquality(t)
+}
+
+// BenchmarkBatchCTXServiceClassCodeEquality benchmarks validating service class code equality
+func BenchmarkBatchCTXServiceClassCodeEquality(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXServiceClassCodeEquality(b)
+	}
+}
+
+// testBatchCTXAddendaCount validates BatchCTX Addendum count of 2
+func testBatchCTXAddendaCount(t testing.TB) {
+	mockBatch := mockBatchCTX()
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Addendum" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXAddendaCount tests validating BatchCTX Addendum count of 2
+func TestBatchCTXAddendaCount(t *testing.T) {
+	testBatchCTXAddendaCount(t)
+}
+
+// BenchmarkBatchCTXAddendaCount benchmarks validating BatchCTX Addendum count of 2
+func BenchmarkBatchCTXAddendaCount(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXAddendaCount(b)
+	}
+}
+
+// testBatchCTXAddendaCountZero validates Addendum count of 0
+func testBatchCTXAddendaCountZero(t testing.TB) {
+	mockBatch := NewBatchCTX(mockBatchCTXHeader())
+	mockBatch.AddEntry(mockCTXEntryDetail())
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Addendum" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXAddendaCountZero tests validating Addendum count of 0
+func TestBatchCTXAddendaCountZero(t *testing.T) {
+	testBatchCTXAddendaCountZero(t)
+}
+
+// BenchmarkBatchCTXAddendaCountZero benchmarks validating Addendum count of 0
+func BenchmarkBatchCTXAddendaCountZero(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXAddendaCountZero(b)
+	}
+}
+
+// testBatchCTXInvalidAddendum validates Addendum must be Addenda05
+func testBatchCTXInvalidAddendum(t testing.TB) {
+	mockBatch := NewBatchCTX(mockBatchCTXHeader())
+	mockBatch.AddEntry(mockCTXEntryDetail())
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda02())
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Addendum" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXInvalidAddendum tests validating Addendum must be Addenda05
+func TestBatchCTXInvalidAddendum(t *testing.T) {
+	testBatchCTXInvalidAddendum(t)
+}
+
+// BenchmarkBatchCTXInvalidAddendum benchmarks validating Addendum must be Addenda05
+func BenchmarkBatchCTXInvalidAddendum(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXInvalidAddendum(b)
+	}
+}
+
+// testBatchCTXInvalidAddenda validates Addendum must be Addenda05 with record type 7
+func testBatchCTXInvalidAddenda(t testing.TB) {
+	mockBatch := NewBatchCTX(mockBatchCTXHeader())
+	mockBatch.AddEntry(mockCTXEntryDetail())
+	addenda05 := mockAddenda05()
+	addenda05.recordType = "63"
+	mockBatch.GetEntries()[0].AddAddenda(addenda05)
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXInvalidAddenda tests validating Addendum must be Addenda05 with record type 7
+func TestBatchCTXInvalidAddenda(t *testing.T) {
+	testBatchCTXInvalidAddenda(t)
+}
+
+// BenchmarkBatchCTXInvalidAddenda benchmarks validating Addendum must be Addenda05 with record type 7
+func BenchmarkBatchCTXInvalidAddenda(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXInvalidAddenda(b)
+	}
+}
+
+// testBatchCTXInvalidBuild validates an invalid batch build
+func testBatchCTXInvalidBuild(t testing.TB) {
+	mockBatch := mockBatchCTX()
+	mockBatch.GetHeader().recordType = "3"
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*FieldError); ok {
+			if e.FieldName != "recordType" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXInvalidBuild tests validating an invalid batch build
+func TestBatchCTXInvalidBuild(t *testing.T) {
+	testBatchCTXInvalidBuild(t)
+}
+
+// BenchmarkBatchCTXInvalidBuild benchmarks validating an invalid batch build
+func BenchmarkBatchCTXInvalidBuild(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXInvalidBuild(b)
+	}
+}
+
+// testBatchCTXAddenda10000 validates error for 10000 Addenda
+func testBatchCTXAddenda10000(t testing.TB) {
+
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 220
+	bh.StandardEntryClassCode = "CTX"
+	bh.CompanyName = "Payee Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "ACH CTX"
+	bh.ODFIIdentification = "12104288"
+
+	entry := NewEntryDetail()
+	entry.TransactionCode = 22
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.IdentificationNumber = "45689033"
+	entry.SetCTXAddendaRecords(9999)
+	entry.SetCTXReceivingCompany("Receiver Company")
+	entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1)
+	entry.DiscretionaryData = "01"
+	entry.Category = CategoryForward
+
+	mockBatch := NewBatchCTX(bh)
+	mockBatch.AddEntry(entry)
+
+	for i := 0; i < 10000; i++ {
+		mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
+	}
+
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Addendum" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXAddenda10000 tests validating error for 10000 Addenda
+func TestBatchCTXAddenda10000(t *testing.T) {
+	testBatchCTXAddenda10000(t)
+}
+
+// BenchmarkBatchCTXAddenda10000 benchmarks validating error for 10000 Addenda
+func BenchmarkBatchCTXAddenda10000(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXAddenda10000(b)
+	}
+}
+
+// testBatchCTXAddendaRecords validates error for AddendaRecords not equal to addendum
+func testBatchCTXAddendaRecords(t testing.TB) {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 220
+	bh.StandardEntryClassCode = "CTX"
+	bh.CompanyName = "Payee Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "ACH CTX"
+	bh.ODFIIdentification = "12104288"
+
+	entry := NewEntryDetail()
+	entry.TransactionCode = 22
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.IdentificationNumber = "45689033"
+	entry.SetCTXAddendaRecords(500)
+	entry.SetCTXReceivingCompany("Receiver Company")
+	entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1)
+	entry.DiscretionaryData = "01"
+	entry.Category = CategoryForward
+
+	mockBatch := NewBatchCTX(bh)
+	mockBatch.AddEntry(entry)
+
+	for i := 0; i < 565; i++ {
+		mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
+	}
+
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Addendum" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXAddendaRecords tests validating error for AddendaRecords not equal to addendum
+func TestBatchCTXAddendaRecords(t *testing.T) {
+	testBatchCTXAddendaRecords(t)
+}
+
+// BenchmarkBatchAddendaRecords benchmarks validating error for AddendaRecords not equal to addendum
+func BenchmarkBatchCTXAddendaRecords(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXAddendaRecords(b)
+	}
+}
+
+// testBatchCTXReceivingCompany validates CTXReceivingCompany
+func testBatchCTXReceivingCompany(t testing.TB) {
+	mockBatch := mockBatchCTX()
+	//mockBatch.GetEntries()[0].SetCTXReceivingCompany("Receiver")
+
+	if mockBatch.GetEntries()[0].CTXReceivingCompanyField() != "Receiver Company" {
+		t.Errorf("expected %v got %v", "Receiver Company", mockBatch.GetEntries()[0].CTXReceivingCompanyField())
+	}
+}
+
+// TestBatchCTXReceivingCompany tests validating CTXReceivingCompany
+func TestBatchCTXReceivingCompany(t *testing.T) {
+	testBatchCTXReceivingCompany(t)
+}
+
+// BenchmarkBatchCTXReceivingCompany benchmarks validating CTXReceivingCompany
+func BenchmarkBatchCTXReceivingCompany(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXReceivingCompany(b)
+	}
+}
+
+// testBatchCTXReserved validates CTXReservedField
+func testBatchCTXReserved(t testing.TB) {
+	mockBatch := mockBatchCTX()
+
+	if mockBatch.GetEntries()[0].CTXReservedField() != "  " {
+		t.Errorf("expected %v got %v", "  ", mockBatch.GetEntries()[0].CTXReservedField())
+	}
+}
+
+// TestBatchCTXReserved tests validating CTXReservedField
+func TestBatchCTXReserved(t *testing.T) {
+	testBatchCTXReserved(t)
+}
+
+// BenchmarkBatchCTXReserved benchmarks validating CTXReservedField
+func BenchmarkBatchCTXReserved(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXReserved(b)
+	}
+}
+
+// testBatchCTXZeroAddendaRecords validates zero addenda records
+func testBatchCTXZeroAddendaRecords(t testing.TB) {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 220
+	bh.StandardEntryClassCode = "CTX"
+	bh.CompanyName = "Payee Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "ACH CTX"
+	bh.ODFIIdentification = "12104288"
+
+	entry := NewEntryDetail()
+	entry.TransactionCode = 22
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.IdentificationNumber = "45689033"
+	entry.SetCTXAddendaRecords(1)
+	entry.SetCTXReceivingCompany("Receiver Company")
+	entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1)
+	entry.DiscretionaryData = "01"
+	entry.Category = CategoryForward
+
+	mockBatch := NewBatchCTX(bh)
+	mockBatch.AddEntry(entry)
+
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Addendum" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXZeroAddendaRecords tests validating zero addenda records
+func TestBatchCTXZeroAddendaRecords(t *testing.T) {
+	testBatchCTXZeroAddendaRecords(t)
+}
+
+// BenchmarkBatchZeroAddendaRecords benchmarks validating zero addenda records
+func BenchmarkBatchCTXZeroAddendaRecords(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXZeroAddendaRecords(b)
+	}
+}
+
+// testBatchCTXPrenoteAddendaRecords validates prenote addenda records
+func testBatchCTXPrenoteAddendaRecords(t testing.TB) {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 220
+	bh.StandardEntryClassCode = "CTX"
+	bh.CompanyName = "Payee Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "ACH CTX"
+	bh.ODFIIdentification = "12104288"
+	bh.OriginatorStatusCode = 2
+
+	entry := NewEntryDetail()
+	entry.TransactionCode = 23
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.IdentificationNumber = "45689033"
+	entry.SetCTXAddendaRecords(1)
+	entry.SetCTXReceivingCompany("Receiver Company")
+	entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1)
+	entry.DiscretionaryData = "01"
+	entry.Category = CategoryForward
+
+	mockBatch := NewBatchCTX(bh)
+	mockBatch.AddEntry(entry)
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
+
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Addendum" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchCTXPrenoteAddendaRecords tests validating prenote addenda records
+func TestBatchCTXPrenoteAddendaRecords(t *testing.T) {
+	testBatchCTXPrenoteAddendaRecords(t)
+}
+
+// BenchmarkBatchPrenoteAddendaRecords benchmarks validating prenote addenda records
+func BenchmarkBatchCTXPrenoteAddendaRecords(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchCTXPrenoteAddendaRecords(b)
+	}
+}
diff --git a/batch_test.go b/batch_test.go
index 24ddeafd..8b752e0f 100644
--- a/batch_test.go
+++ b/batch_test.go
@@ -63,6 +63,7 @@ func mockBatchInvalidSECHeader() *BatchHeader {
 }
 
 // Test cases that apply to all batch types
+// testBatchNumberMismatch validates BatchNumber mismatch
 func testBatchNumberMismatch(t testing.TB) {
 	mockBatch := mockBatch()
 	mockBatch.GetControl().BatchNumber = 2
@@ -77,9 +78,12 @@ func testBatchNumberMismatch(t testing.TB) {
 	}
 }
 
+// TestBatchNumberMismatch tests validating BatchNumber mismatch
 func TestBatchNumberMismatch(t *testing.T) {
 	testBatchNumberMismatch(t)
 }
+
+// BenchmarkBatchNumberMismatch benchmarks validating BatchNumber mismatch
 func BenchmarkBatchNumberMismatch(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
@@ -87,7 +91,8 @@ func BenchmarkBatchNumberMismatch(b *testing.B) {
 	}
 }
 
-func testCreditBatchisBatchAmount(t testing.TB) {
+// testCreditBatchIsBatchAmount validates Batch TotalCreditEntryDollarAmount
+func testCreditBatchIsBatchAmount(t testing.TB) {
 	mockBatch := mockBatch()
 	mockBatch.SetHeader(mockBatchHeader())
 	e1 := mockBatch.GetEntries()[0]
@@ -114,19 +119,22 @@ func testCreditBatchisBatchAmount(t testing.TB) {
 	}
 }
 
-func TestCreditBatchisBatchAmount(t *testing.T) {
-	testCreditBatchisBatchAmount(t)
+// TestCreditBatchIsBatchAmount test validating Batch TotalCreditEntryDollarAmount
+func TestCreditBatchIsBatchAmount(t *testing.T) {
+	testCreditBatchIsBatchAmount(t)
 }
 
-func BenchmarkCreditBatchisBatchAmount(b *testing.B) {
+// BenchmarkCreditBatchIsBatchAmount benchmarks Batch TotalCreditEntryDollarAmount
+func BenchmarkCreditBatchIsBatchAmount(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
-		testCreditBatchisBatchAmount(b)
+		testCreditBatchIsBatchAmount(b)
 	}
 
 }
 
-func testSavingsBatchisBatchAmount(t testing.TB) {
+// testSavingsBatchIsBatchAmount validates Batch TotalDebitEntryDollarAmount
+func testSavingsBatchIsBatchAmount(t testing.TB) {
 	mockBatch := mockBatch()
 	mockBatch.SetHeader(mockBatchHeader())
 	e1 := mockBatch.GetEntries()[0]
@@ -154,18 +162,20 @@ func testSavingsBatchisBatchAmount(t testing.TB) {
 	}
 }
 
-func TestSavingsBatchisBatchAmount(t *testing.T) {
-	testSavingsBatchisBatchAmount(t)
+// TestSavingsBatchIsBatchAmount tests validating Batch TotalDebitEntryDollarAmount
+func TestSavingsBatchIsBatchAmount(t *testing.T) {
+	testSavingsBatchIsBatchAmount(t)
 }
 
-func BenchmarkSavingsBatchisBatchAmount(b *testing.B) {
+// BenchmarkSavingsBatchIsBatchAmount benchmarks validating Batch TotalDebitEntryDollarAmount
+func BenchmarkSavingsBatchIsBatchAmount(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
-		testSavingsBatchisBatchAmount(b)
+		testSavingsBatchIsBatchAmount(b)
 	}
 }
 
-func testBatchisEntryHash(t testing.TB) {
+func testBatchIsEntryHash(t testing.TB) {
 	mockBatch := mockBatch()
 	mockBatch.GetControl().EntryHash = 1
 	if err := mockBatch.verify(); err != nil {
@@ -179,14 +189,14 @@ func testBatchisEntryHash(t testing.TB) {
 	}
 }
 
-func TestBatchisEntryHash(t *testing.T) {
-	testBatchisEntryHash(t)
+func TestBatchIsEntryHash(t *testing.T) {
+	testBatchIsEntryHash(t)
 }
 
-func BenchmarkBatchisEntryHash(b *testing.B) {
+func BenchmarkBatchIsEntryHash(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
-		testBatchisEntryHash(b)
+		testBatchIsEntryHash(b)
 	}
 }
 
@@ -395,6 +405,8 @@ func BenchmarkBatchAddendaTraceNumber(b *testing.B) {
 		testBatchAddendaTraceNumber(b)
 	}
 }
+
+// testNewBatchDefault validates error for NewBatch if invalid SEC Code
 func testNewBatchDefault(t testing.TB) {
 	_, err := NewBatch(mockBatchInvalidSECHeader())
 
@@ -407,10 +419,13 @@ func testNewBatchDefault(t testing.TB) {
 	}
 }
 
+// TestNewBatchDefault test validating error for NewBatch if invalid SEC Code
 func TestNewBatchDefault(t *testing.T) {
 	testNewBatchDefault(t)
 }
 
+// BenchmarkNewBatchDefault benchmarks validating error for NewBatch if
+// invalid SEC Code
 func BenchmarkNewBatchDefault(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
@@ -418,6 +433,7 @@ func BenchmarkNewBatchDefault(b *testing.B) {
 	}
 }
 
+// testBatchCategory validates Batch Category
 func testBatchCategory(t testing.TB) {
 	mockBatch := mockBatch()
 	// Add a Addenda Return to the mock batch
@@ -434,10 +450,12 @@ func testBatchCategory(t testing.TB) {
 	}
 }
 
+// TestBatchCategory tests validating Batch Category
 func TestBatchCategory(t *testing.T) {
 	testBatchCategory(t)
 }
 
+// BenchmarkBatchCategory benchmarks validating Batch Category
 func BenchmarkBatchCategory(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
@@ -445,6 +463,7 @@ func BenchmarkBatchCategory(b *testing.B) {
 	}
 }
 
+//  testBatchCategoryForwardReturn validates Category based on EntryDetail
 func testBatchCategoryForwardReturn(t testing.TB) {
 	mockBatch := mockBatch()
 	// Add a Addenda Return to the mock batch
@@ -466,9 +485,12 @@ func testBatchCategoryForwardReturn(t testing.TB) {
 	}
 }
 
+// TestBatchCategoryForwardReturn tests validating Category based on EntryDetail
 func TestBatchCategoryForwardReturn(t *testing.T) {
 	testBatchCategoryForwardReturn(t)
 }
+
+//  BenchmarkBatchCategoryForwardReturn benchmarks validating Category based on EntryDetail
 func BenchmarkBatchCategoryForwardReturn(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
@@ -546,6 +568,7 @@ func BenchmarkBatchInvalidTraceNumberODFI(b *testing.B) {
 	}
 }
 
+// testBatchNoEntry validates error for a batch with no entries
 func testBatchNoEntry(t testing.TB) {
 	mockBatch := mockBatchNoEntry()
 	if err := mockBatch.build(); err != nil {
@@ -557,12 +580,26 @@ func testBatchNoEntry(t testing.TB) {
 			t.Errorf("%T: %s", err, err)
 		}
 	}
+
+	// test verify
+	if err := mockBatch.verify(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "entries" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+
 }
 
+// TestBatchNoEntry tests validating error for a batch with no entries
 func TestBatchNoEntry(t *testing.T) {
 	testBatchNoEntry(t)
 }
 
+// BenchmarkBatchNoEntry benchmarks validating error for a batch with no entries
 func BenchmarkBatchNoEntry(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
@@ -597,3 +634,38 @@ func BenchmarkBatchControl(b *testing.B) {
 		testBatchControl(b)
 	}
 }
+
+// testIATBatch validates an IAT batch returns an error for batch
+func testIATBatch(t testing.TB) {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 220
+	bh.StandardEntryClassCode = "IAT"
+	bh.CompanyName = "ACME Corporation"
+	bh.CompanyIdentification = "123456789"
+	bh.CompanyEntryDescription = "PAYROLL"
+	bh.EffectiveEntryDate = time.Now()
+	bh.ODFIIdentification = "123456789"
+
+	_, err := NewBatch(bh)
+
+	if e, ok := err.(*FileError); ok {
+		if e.FieldName != "StandardEntryClassCode" {
+			t.Errorf("%T: %s", err, err)
+		}
+	} else {
+		t.Errorf("%T: %s", err, err)
+	}
+}
+
+// TestIATBatch tests validating an IAT batch returns an error for batch
+func TestIATBatch(t *testing.T) {
+	testIATBatch(t)
+}
+
+// BenchmarkIATBatch benchmarks validating an IAT batch returns an error for batch
+func BenchmarkIATBatch(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testIATBatch(b)
+	}
+}
diff --git a/iatEntryDetail_test.go b/iatEntryDetail_test.go
index 117b2de8..b6ceed1b 100644
--- a/iatEntryDetail_test.go
+++ b/iatEntryDetail_test.go
@@ -78,7 +78,7 @@ func BenchmarkIATMockEntryDetail(b *testing.B) {
 func testParseIATEntryDetail(t testing.TB) {
 	var line = "6221210428820007             000010000012345678901234567890123456789012345    1231380100000001"
 	r := NewReader(strings.NewReader(line))
-	r.addIATCurrentBatch(NewIATBatch(mockIATBatchHeaderFF()))
+	r.addIATCurrentBatch(IATNewBatch(mockIATBatchHeaderFF()))
 	r.IATCurrentBatch.SetHeader(mockIATBatchHeaderFF())
 	r.line = line
 	if err := r.parseIATEntryDetail(); err != nil {
@@ -134,7 +134,7 @@ func BenchmarkParseIATEntryDetail(b *testing.B) {
 func testIATEDString(t testing.TB) {
 	var line = "6221210428820007             000010000012345678901234567890123456789012345    1231380100000001"
 	r := NewReader(strings.NewReader(line))
-	r.addIATCurrentBatch(NewIATBatch(mockIATBatchHeaderFF()))
+	r.addIATCurrentBatch(IATNewBatch(mockIATBatchHeaderFF()))
 	r.IATCurrentBatch.SetHeader(mockIATBatchHeaderFF())
 	r.line = line
 	if err := r.parseIATEntryDetail(); err != nil {
diff --git a/test/ach-iat-read/iat-ach.ach b/test/ach-iat-read/iat-ach.ach
deleted file mode 100644
index 838ccda7..00000000
--- a/test/ach-iat-read/iat-ach.ach
+++ /dev/null
@@ -1,20 +0,0 @@
-101 121042882 2313801041807260000A094101Bank                   My Bank Name                   
-5220                FF3               US123456789 IATTRADEPAYMTCADUSD010101   0231380100000001
-6271210428820007             0000100000123456789                              1231380100000001
-710ANN000000000000100000928383-23938          BEK Enterprises                          0000001
-711BEK Solutions                      15 West Place Street                             0000001
-712JacobsTown*PA\                     US*19305\                                        0000001
-713Wells Fargo                        01231380104                         US           0000001
-714Citadel Bank                       01121042882                         CA           0000001
-7159874654932139872121 Front Street                                                    0000001
-716LetterTown*AB\                     CA*80014\                                        0000001
-717This is an international payment                                                00010000001
-718Bank of France                     01456456456987987                   FR       00010000001
-82200000100012104288000000100000000000000000                                   231380100000001
-9000001000002000000100012104288000000100000000000000000                                       
-9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
-9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
-9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
-9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
-9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
-9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
\ No newline at end of file
diff --git a/test/ach-iat-read/main.go b/test/ach-iat-read/main.go
deleted file mode 100644
index 8dabd960..00000000
--- a/test/ach-iat-read/main.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"github.com/moov-io/ach"
-	"log"
-	"os"
-)
-
-func main() {
-	// open a file for reading. Any io.Reader Can be used
-	f, err := os.Open("iat-ach.ach")
-	if err != nil {
-		log.Fatal(err)
-	}
-	r := ach.NewReader(f)
-	achFile, err := r.Read()
-	if err != nil {
-		fmt.Printf("Issue reading file: %+v \n", err)
-	}
-	// ensure we have a validated file structure
-	if achFile.Validate(); err != nil {
-		fmt.Printf("Could not validate entire read file: %v", err)
-	}
-	// If you trust the file but it's formatting is off building will probably resolve the malformed file.
-	if achFile.Create(); err != nil {
-		fmt.Printf("Could not build file with read properties: %v", err)
-	}
-
-	fmt.Printf("Total Amount Debit: %v \n", achFile.Control.TotalDebitEntryDollarAmountInFile)
-	fmt.Printf("SEC Code: %v \n", achFile.IATBatches[0].GetHeader().StandardEntryClassCode)
-	fmt.Printf("Entry: %v \n", achFile.IATBatches[0].GetEntries()[0])
-	fmt.Printf("Addenda Record Indicator: %v \n", achFile.IATBatches[0].GetEntries()[0].AddendaRecordIndicator)
-	fmt.Printf("Addenda10: %v \n", achFile.IATBatches[0].GetEntries()[0].Addenda10)
-	fmt.Printf("Addenda11: %v \n", achFile.IATBatches[0].GetEntries()[0].Addenda11)
-	fmt.Printf("Addenda12: %v \n", achFile.IATBatches[0].GetEntries()[0].Addenda12)
-	fmt.Printf("Addenda13: %v \n", achFile.IATBatches[0].GetEntries()[0].Addenda13)
-	fmt.Printf("Addenda14: %v \n", achFile.IATBatches[0].GetEntries()[0].Addenda14)
-	fmt.Printf("Addenda15: %v \n", achFile.IATBatches[0].GetEntries()[0].Addenda15)
-	fmt.Printf("Addenda16: %v \n", achFile.IATBatches[0].GetEntries()[0].Addenda16)
-	fmt.Printf("Addenda17: %v \n", achFile.IATBatches[0].GetEntries()[0].Addendum[0].String())
-	fmt.Printf("Addenda18: %v \n", achFile.IATBatches[0].GetEntries()[0].Addendum[1].String())
-}
diff --git a/test/ach-iat-read/main_test.go b/test/ach-iat-read/main_test.go
deleted file mode 100644
index 45fcc779..00000000
--- a/test/ach-iat-read/main_test.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package main
-
-import "testing"
-
-func Test(t *testing.T) {
-	main()
-}
diff --git a/test/ach-iat-write/main.go b/test/ach-iat-write/main.go
deleted file mode 100644
index 90608201..00000000
--- a/test/ach-iat-write/main.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package main
-
-import (
-	"time"
-	"github.com/moov-io/ach"
-	"log"
-	"os"
-)
-
-func main() {
-	// Example transfer to write an ACH IAT file to debit a external institutions account
-	// Important: All financial institutions are different and will require registration and exact field values.
-
-	// Set originator bank ODFI and destination Operator for the financial institution
-	// this is the funding/receiving source of the transfer
-	fh := ach.NewFileHeader()
-	fh.ImmediateDestination = "121042882" // Routing Number of the ACH Operator or receiving point to which the file is being sent
-	fh.ImmediateOrigin = "231380104" // Routing Number of the ACH Operator or sending point that is sending the file
-	fh.FileCreationDate = time.Now()      // Today's Date
-	fh.ImmediateDestinationName = "Bank"
-	fh.ImmediateOriginName = "My Bank Name"
-
-	// BatchHeader identifies the originating entity and the type of transactions contained in the batch
-	bh := ach.NewIATBatchHeader()
-	bh.ServiceClassCode = 220
-	bh.ForeignExchangeIndicator = "FF"
-	bh.ForeignExchangeReferenceIndicator = 3
-	bh.ISODestinationCountryCode = "US"
-	bh.OriginatorIdentification = "123456789"
-	bh.StandardEntryClassCode = "IAT"
-	bh.CompanyEntryDescription = "TRADEPAYMT"
-	bh.ISOOriginatingCurrencyCode = "CAD"
-	bh.ISODestinationCurrencyCode = "USD"
-	bh.ODFIIdentification = "23138010"
-
-	// Identifies the receivers account information
-	// can be multiple entry's per batch
-	entry := ach.NewIATEntryDetail()
-	entry.TransactionCode = 27
-	entry.SetRDFI("121042882")
-	entry.AddendaRecords = 007
-	entry.DFIAccountNumber = "123456789"
-	entry.Amount = 100000 // 1000.00
-	entry.SetTraceNumber("23138010", 1)
-	entry.Category = ach.CategoryForward
-
-	//addenda
-
-	addenda10 := ach.NewAddenda10()
-	addenda10.TransactionTypeCode = "ANN"
-	addenda10.ForeignPaymentAmount = 100000
-	addenda10.ForeignTraceNumber = "928383-23938"
-	addenda10.Name = "BEK Enterprises"
-	addenda10.EntryDetailSequenceNumber = 00000001
-	entry.Addenda10 = addenda10
-
-	addenda11 := ach.NewAddenda11()
-	addenda11.OriginatorName = "BEK Solutions"
-	addenda11.OriginatorStreetAddress = "15 West Place Street"
-	addenda11.EntryDetailSequenceNumber = 00000001
-	entry.Addenda11 = addenda11
-
-	addenda12 := ach.NewAddenda12()
-	addenda12.OriginatorCityStateProvince = "JacobsTown*PA\\"
-	addenda12.OriginatorCountryPostalCode = "US*19305\\"
-	addenda12.EntryDetailSequenceNumber = 00000001
-	entry.Addenda12 = addenda12
-
-	addenda13 := ach.NewAddenda13()
-	addenda13.ODFIName = "Wells Fargo"
-	addenda13.ODFIIDNumberQualifier = "01"
-	addenda13.ODFIIdentification = "231380104"
-	addenda13.ODFIBranchCountryCode = "US"
-	addenda13.EntryDetailSequenceNumber = 00000001
-	entry.Addenda13 = addenda13
-
-	addenda14 := ach.NewAddenda14()
-	addenda14.RDFIName = "Citadel Bank"
-	addenda14.RDFIIDNumberQualifier = "01"
-	addenda14.RDFIIdentification = "121042882"
-	addenda14.RDFIBranchCountryCode = "CA"
-	addenda14.EntryDetailSequenceNumber = 00000001
-	entry.Addenda14 = addenda14
-
-	addenda15 := ach.NewAddenda15()
-	addenda15.ReceiverIDNumber = "987465493213987"
-	addenda15.ReceiverStreetAddress = "2121 Front Street"
-	addenda15.EntryDetailSequenceNumber = 00000001
-	entry.Addenda15 = addenda15
-
-	addenda16 := ach.NewAddenda16()
-	addenda16.ReceiverCityStateProvince = "LetterTown*AB\\"
-	addenda16.ReceiverCountryPostalCode = "CA*80014\\"
-	addenda16.EntryDetailSequenceNumber = 00000001
-	entry.Addenda16 = addenda16
-
-	addenda17 := ach.NewAddenda17()
-	addenda17.PaymentRelatedInformation = "This is an international payment"
-	addenda17.SequenceNumber = 1
-	addenda17.EntryDetailSequenceNumber = 0000001
-	entry.AddIATAddenda(addenda17)
-
-	addenda18 := ach.NewAddenda18()
-	addenda18.ForeignCorrespondentBankName = "Bank of France"
-	addenda18.ForeignCorrespondentBankIDNumberQualifier = "01"
-	addenda18.ForeignCorrespondentBankIDNumber = "456456456987987"
-	addenda18.ForeignCorrespondentBankBranchCountryCode = "FR"
-	addenda18.SequenceNumber = 3
-	addenda18.EntryDetailSequenceNumber = 0000001
-	entry.AddIATAddenda(addenda18)
-
-	// build the batch
-	batch := ach.NewIATBatch(bh)
-	batch.AddEntry(entry)
-	if err := batch.Create(); err != nil {
-		log.Fatalf("Unexpected error building batch: %s\n", err)
-	}
-
-	// build the file
-	file := ach.NewFile()
-	file.SetHeader(fh)
-	file.AddIATBatch(batch)
-	if err := file.Create(); err != nil {
-		log.Fatalf("Unexpected error building file: %s\n", err)
-	}
-
-	// write the file to std out. Anything io.Writer
-	w := ach.NewWriter(os.Stdout)
-	if err := w.Write(file); err != nil {
-		log.Fatalf("Unexpected error: %s\n", err)
-	}
-	w.Flush()
-}
diff --git a/test/ach-iat-write/main_test.go b/test/ach-iat-write/main_test.go
deleted file mode 100644
index 45fcc779..00000000
--- a/test/ach-iat-write/main_test.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package main
-
-import "testing"
-
-func Test(t *testing.T) {
-	main()
-}