Repository: moov-io/ach
Pull request #202 (2018-06-01 00:20:00)

SEC Codes ARC, BOC, RCK
#188 RCK, #190 BOC, #191 ARC


Patch:
diff --git a/.travis.yml b/.travis.yml
index 9e08c5e4..13383adf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,8 @@
 language: go
 sudo: false
 go:
-- tip
+- 1.9.x
+- 1.10.x
 env:
   global:
     secure: QPkcX77j8QEqTwOYyLGItqvxYwE6Na5WaSZWjmhp48OlxYatWRHxJBwcFYSn1OWD5FMn+3oW39fHknReIxtrnhXMaNvI7x3/0gy4zujD/xZ2xAg7NsQ+l5buvEFO8/LEwwo0fp4knItFcBv8xH/ziJBJyXvgfMtj7Is4Q/pB1p6pWDdVy1vtAj3zH02bcqh1yXXS3HvcD8UhTszfU017gVNXDN1ow0rp1L3ainr3btrVK9izUxZfKvb7PlWJO1ogah7xNr/dIOJLsx2SfKgzKp+3H28L2WegtbzON74Op4jXvRywCwqjmUt/nwJ/Y9anunMNHT136h+ye4ziG1i/VdbWq0Q4PopQ8yYqinujG7SjfQio+wNCV2cwc2r/WjNBjbH0N9/Pflogq3RHvgy/9VtPif1tY+RrZCSntohoEZbYpVcFQFE1xDyf6xq/uLxVeEcCU33gqq7cKEfpcUgyCITa+yCPfBdtgkLBJ8h7Sew1j08D1kTKUW6g3D1epmwlCh/Z16oHG5VwSnCLGDjJy8wm/hQk1i/g7qeP7g24CfNzffzlFBCy88HhjzmrhUpcaTyfVVDf4h8wK6Zu/J3dHjHXQYwfiQRqpMa+2DYyjGgZhniccuh4GWolGZauDQdmO9SD4Ugyt9PEMk02i32ax3A4XE/Q6VNOam+qszviX3Q=
@@ -12,13 +13,13 @@ before_install:
 - go get -u golang.org/x/lint/golint
 - go get github.com/fzipp/gocyclo
 before_script:
-- GO_FILES=$(find . -iname '*.go' -type f | grep -v /example/ | grep -v /cmd/)
+- GO_FILES=$(find . -iname '*.go' -type f | grep -v /test/ | grep -v /cmd/ | grep -v /example/)
 script:
 - test -z $(gofmt -s -l $GO_FILES)
 - go vet $GO_FILES
 - megacheck $GO_FILES
 - misspell -error -locale US .
 - gocyclo -over 19 $GO_FILES
-- golint -set_exit_status $(go list $GO_FILES)
+- golint -set_exit_status $GO_FILES
 after_success:
 - goveralls -repotoken $COVERALLS_TOKEN
diff --git a/README.md b/README.md
index 1df470fa..b0a93294 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,9 @@ ACH is under active development but already in production for multiple companies
 	* CCD (Corporate credit or debit)
 	* TEL (Telephone-Initiated Entry)
 	* COR (Automated Notification of Change(NOC))
+	* RCK (Represented Check Entries)
+	* BOC (Back Office Conversion)
+	* ARC (Accounts Receivable Entry)
 	* Return Entries
 
 
diff --git a/addenda99.go b/addenda99.go
index 87e4b402..7c908311 100644
--- a/addenda99.go
+++ b/addenda99.go
@@ -169,10 +169,12 @@ func makeReturnCodeDict() map[string]*returnCode {
 		// Return Reason Codes for RDFIs
 		{"R01", "Insufficient Funds", "Available balance is not sufficient to cover the dollar value of the debit entry"},
 		{"R02", "Account Closed", "Previously active account has been closed by customer or RDFI"},
+		// R03 may not be used to return ARC, BOC or POP entries solely because they do not contain an Individual Name.
 		{"R03", "No Account/Unable to Locate Account", "Account number structure is valid and passes editing process, but does not correspond to individual or is not an open account"},
 		{"R04", "Invalid Account Number", "Account number structure not valid; entry may fail check digit validation or may contain an incorrect number of digits."},
 		{"R05", "Improper Debit to Consumer Account", "A CCD, CTX, or CBR debit entry was transmitted to a Consumer Account of the Receiver and was not authorized by the Receiver"},
 		{"R06", "Returned per ODFI's Request", "ODFI has requested RDFI to return the ACH entry (optional to RDFI - ODFI indemnifies RDFI)}"},
+		// R07 Prohibited use for ARC, BOC, POP and RCK.
 		{"R07", "Authorization Revoked by Customer", "Consumer, who previously authorized ACH payment, has revoked authorization from Originator (must be returned no later than 60 days from settlement date and customer must sign affidavit)"},
 		{"R08", "Payment Stopped", "Receiver of a recurring debit transaction has stopped payment to a specific ACH debit. RDFI should verify the Receiver's intent when a request for stop payment is made to insure this is not intended to be a revocation of authorization"},
 		{"R09", "Uncollected Funds", "Sufficient book or ledger balance exists to satisfy dollar value of the transaction, but the dollar value of transaction is in process of collection (i.e., uncollected checks) or cash reserve balance below dollar value of the debit entry."},
@@ -202,6 +204,13 @@ func makeReturnCodeDict() map[string]*returnCode {
 		{"R33", "Return of XCK entry", "RDFI determines at its sole discretion to return an XCK entry; an XCK return entry may be initiated by midnight of the sixtieth day following the settlement date if the XCK entry"},
 		{"R34", "Limited participation RDFI", "RDFI participation has been limited by a federal or state supervisor"},
 		{"R35", "Return of improper debit entry", "ACH debit not permitted for use with the CIE standard entry class code (except for reversals)"},
+		{"R37", "Source Document Presented for Payment (Adjustment Entry)", "The source document to which an ARC, BOC or POP entry relateshas been presented for payment. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date"},
+		{"R38", "Stop Payment on Source Document (Adjustment Entry)", "A stop payment has been placed on the source document to which the ARC or BOC entry relates. RDFI must return no later than 60 days following Settlement Date. No Written Statement is required as the original stop payment form covers the return"},
+		{"R39", "Improper Source Document", "The RDFI has determined the source document used for the ARC, BOC or POP entry to its Receiver’s account is improper."},
+		{"R50", "State Law Affecting RCK Acceptance", "RDFI is located in a state that has not adopted Revised Article 4 of the UCC or the RDFI is located in a state that requires all canceled checks to be returned within the periodic statement"},
+		{"R51", "Item Related to RCK Entry is Ineligible or RCK Entry is Improper", "The item to which the RCK entry relates was not eligible, Originator did not provide notice of the RCK policy, signature on the item was not genuine, the item has been altered or amount of the entry was not accurately obtained from the item. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date"},
+		{"R52", "Stop Payment on Item (Adjustment Entry)", "A stop payment has been placed on the item to which the RCK entry relates. RDFI must return no later than 60 days following Settlement Date. No Written Statement is required as the original stop payment form covers the return."},
+		{"R53", "Item and RCK Entry Presented for Payment (Adjustment Entry)", "Both the RCK entry and check have been presented forpayment. RDFI must obtain a Written Statement and return the entry within 60 days following Settlement Date"},
 		// More return codes will be added when more SEC types are added to the library.
 	}
 	// populate the map
diff --git a/batch.go b/batch.go
index fdf23514..c78d23dd 100644
--- a/batch.go
+++ b/batch.go
@@ -27,16 +27,22 @@ type batch struct {
 // NewBatch 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 NewBatch(bh *BatchHeader) (Batcher, error) {
 	switch bh.StandardEntryClassCode {
-	case "PPD":
-		return NewBatchPPD(bh), nil
-	case "WEB":
-		return NewBatchWEB(bh), nil
+	case "ARC":
+		return NewBatchARC(bh), nil
+	case "BOC":
+		return NewBatchBOC(bh), nil
 	case "CCD":
 		return NewBatchCCD(bh), nil
 	case "COR":
 		return NewBatchCOR(bh), nil
+	case "PPD":
+		return NewBatchPPD(bh), nil
+	case "RCK":
+		return NewBatchRCK(bh), nil
 	case "TEL":
 		return NewBatchTEL(bh), nil
+	case "WEB":
+		return NewBatchWEB(bh), nil
 	default:
 	}
 	msg := fmt.Sprintf(msgFileNoneSEC, bh.StandardEntryClassCode)
@@ -65,7 +71,7 @@ func (batch *batch) verify() error {
 		msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.Header.CompanyIdentification, batch.Control.CompanyIdentification)
 		return &BatchError{BatchNumber: batchNumber, FieldName: "CompanyIdentification", Msg: msg}
 	}
-	// Control ODFI Identification must be the same as batch header
+	// Control ODFIIdentification must be the same as batch header
 	if batch.Header.ODFIIdentification != batch.Control.ODFIIdentification {
 		msg := fmt.Sprintf(msgBatchHeaderControlEquality, batch.Header.ODFIIdentification, batch.Control.ODFIIdentification)
 		return &BatchError{BatchNumber: batchNumber, FieldName: "ODFIIdentification", Msg: msg}
@@ -310,7 +316,7 @@ func (batch *batch) isOriginatorDNE() error {
 }
 
 // isTraceNumberODFI checks if the first 8 positions of the entry detail trace number
-// match the batch header odfi
+// match the batch header ODFI
 func (batch *batch) isTraceNumberODFI() error {
 	for _, entry := range batch.Entries {
 		if batch.Header.ODFIIdentificationField() != entry.TraceNumberField()[:8] {
@@ -331,7 +337,7 @@ func (batch *batch) isAddendaSequence() error {
 				return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "AddendaRecordIndicator", Msg: msgBatchAddendaIndicator}
 			}
 			lastSeq := -1
-			// check if sequence is assending
+			// check if sequence is ascending
 			for _, addenda := range entry.Addendum {
 				// sequences don't exist in NOC or Return addenda
 				if a, ok := addenda.(*Addenda05); ok {
@@ -353,7 +359,7 @@ func (batch *batch) isAddendaSequence() error {
 	return nil
 }
 
-// isAddendaCount iterates through each entry detail and checks the number of addendum is greater than the count paramater otherwise it returns an error.
+// isAddendaCount iterates through each entry detail and checks the number of addendum is greater than the count parameter otherwise it returns an error.
 // Following SEC codes allow for none or one Addendum
 // "PPD", "WEB", "CCD", "CIE", "DNE", "MTE", "POS", "SHR"
 func (batch *batch) isAddendaCount(count int) error {
@@ -367,7 +373,7 @@ func (batch *batch) isAddendaCount(count int) error {
 	return nil
 }
 
-// isTypeCode takes a typecode string and verifies addenda records match
+// isTypeCode takes a TypeCode string and verifies Addenda records match
 func (batch *batch) isTypeCode(typeCode string) error {
 	for _, entry := range batch.Entries {
 		for _, addenda := range entry.Addendum {
diff --git a/batchARC.go b/batchARC.go
new file mode 100644
index 00000000..1836b2f1
--- /dev/null
+++ b/batchARC.go
@@ -0,0 +1,92 @@
+// 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"
+
+// BatchARC holds the BatchHeader and BatchControl and all EntryDetail for ARC Entries.
+//
+// Accounts Receivable Entry (ARC). A consumer check converted to a one-time ACH debit.
+// The Accounts Receivable (ARC) Entry provides billers the opportunity to initiate single-entry ACH
+// debits to customer accounts by converting checks at the point of receipt through the U.S. mail, at
+// a drop box location or in-person for payment of a bill at a manned location. The biller is required
+// to provide the customer with notice prior to the acceptance of the check that states the receipt of
+// the customer’s check will be deemed as the authorization for an ARC debit entry to the customer’s
+// account. The provision of the notice and the receipt of the check together constitute authorization
+// for the ARC entry. The customer’s check is solely be used as a source document to obtain the routing
+// number, account number and check serial number.
+//
+// The difference between ARC and POP is that ARC can result from a check mailed in whereas POP is in-person.
+type BatchARC struct {
+	batch
+}
+
+// NewBatchARC returns a *BatchARC
+func NewBatchARC(bh *BatchHeader) *BatchARC {
+	batch := new(BatchARC)
+	batch.SetControl(NewBatchControl())
+	batch.SetHeader(bh)
+	return batch
+}
+
+// Validate checks valid NACHA batch rules. Assumes properly parsed records.
+func (batch *BatchARC) 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.
+
+	// Batch ARC cannot have an addenda record
+	if err := batch.isAddendaCount(0); err != nil {
+		return err
+	}
+
+	// Add type specific validation.
+	if batch.Header.StandardEntryClassCode != "ARC" {
+		msg := fmt.Sprintf(msgBatchSECType, batch.Header.StandardEntryClassCode, "ARC")
+		return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg}
+	}
+
+	// RCK detail entries can only be a debit, ServiceClassCode must allow debits
+	switch batch.Header.ServiceClassCode {
+	case 200, 220, 280:
+		msg := fmt.Sprintf(msgBatchServiceClassCode, batch.Header.ServiceClassCode, "ARC")
+		return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "ServiceClassCode", Msg: msg}
+	}
+
+	for _, entry := range batch.Entries {
+		// ARC detail entries must be a debit
+		if entry.CreditOrDebit() != "D" {
+			msg := fmt.Sprintf(msgBatchTransactionCodeCredit, entry.TransactionCode)
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "TransactionCode", Msg: msg}
+		}
+
+		// Amount must be 25,000 or less
+		if entry.Amount > 2500000 {
+			msg := fmt.Sprintf(msgBatchAmount, "25,000", "ARC")
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Amount", Msg: msg}
+		}
+
+		// CheckSerialNumber underlying IdentificationNumber, must be defined
+		if entry.IdentificationNumber == "" {
+			msg := fmt.Sprintf(msgBatchCheckSerialNumber, "ARC")
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "CheckSerialNumber", Msg: msg}
+		}
+	}
+	return nil
+}
+
+// Create takes Batch Header and Entries and builds a valid batch
+func (batch *BatchARC) 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/batchBOC.go b/batchBOC.go
new file mode 100644
index 00000000..e3994152
--- /dev/null
+++ b/batchBOC.go
@@ -0,0 +1,97 @@
+// 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"
+
+// BatchBOC holds the BatchHeader and BatchControl and all EntryDetail for BOC Entries.
+//
+// Back Office Conversion (BOC) A single entry debit initiated at the point of purchase
+// or at a manned bill payment location to transfer funds through conversion to an
+// ACH debit entry during back office processing.
+//
+// BOC allows retailers/billers, and ODFIs acting as Originators,
+// to electronically convert checks received at the point-of-purchase as well as at a
+// manned bill payment location into a single-entry ACH debit. The authorization to
+// convert the check will be obtained through a notice at the checkout or manned bill
+// payment location (e.g., loan payment at financial institution’s teller window) and the
+// receipt of the Receiver’s check. The decision to process the check item as an ACH debit
+// will be made in the “back office” instead of at the point-of-purchase. The customer’s
+// check will solely be used as a source document to obtain the routing number, account
+// number and check serial number.
+//
+// Unlike ARC entries, BOC conversions require the customer to be present and a notice that
+// checks may be converted to BOC ACH entries be posted.
+type BatchBOC struct {
+	batch
+}
+
+// NewBatchBOC returns a *BatchBOC
+func NewBatchBOC(bh *BatchHeader) *BatchBOC {
+	batch := new(BatchBOC)
+	batch.SetControl(NewBatchControl())
+	batch.SetHeader(bh)
+	return batch
+}
+
+// Validate checks valid NACHA batch rules. Assumes properly parsed records.
+func (batch *BatchBOC) 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.
+
+	// Batch BOC cannot have an addenda record
+	if err := batch.isAddendaCount(0); err != nil {
+		return err
+	}
+
+	// Add type specific validation.
+	if batch.Header.StandardEntryClassCode != "BOC" {
+		msg := fmt.Sprintf(msgBatchSECType, batch.Header.StandardEntryClassCode, "BOC")
+		return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg}
+	}
+
+	// BOC detail entries can only be a debit, ServiceClassCode must allow debits
+	switch batch.Header.ServiceClassCode {
+	case 200, 220, 280:
+		msg := fmt.Sprintf(msgBatchServiceClassCode, batch.Header.ServiceClassCode, "RCK")
+		return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "ServiceClassCode", Msg: msg}
+	}
+
+	for _, entry := range batch.Entries {
+		// BOC detail entries must be a debit
+		if entry.CreditOrDebit() != "D" {
+			msg := fmt.Sprintf(msgBatchTransactionCodeCredit, entry.TransactionCode)
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "TransactionCode", Msg: msg}
+		}
+
+		// Amount must be 25,000 or less
+		if entry.Amount > 2500000 {
+			msg := fmt.Sprintf(msgBatchAmount, "25,000", "BOC")
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Amount", Msg: msg}
+		}
+
+		// CheckSerialNumber underlying IdentificationNumber, must be defined
+		if entry.IdentificationNumber == "" {
+			msg := fmt.Sprintf(msgBatchCheckSerialNumber, "BOC")
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "CheckSerialNumber", Msg: msg}
+		}
+	}
+	return nil
+}
+
+// Create takes Batch Header and Entries and builds a valid batch
+func (batch *BatchBOC) 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/batchRCK.go b/batchRCK.go
new file mode 100644
index 00000000..42642db5
--- /dev/null
+++ b/batchRCK.go
@@ -0,0 +1,88 @@
+// 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"
+
+// BatchRCK holds the BatchHeader and BatchControl and all EntryDetail for RCK Entries.
+//
+// Represented Check Entries (RCK). A physical check that was presented but returned because of
+// insufficient funds may be represented as an ACH entry.
+type BatchRCK struct {
+	batch
+}
+
+// NewBatchRCK returns a *BatchRCK
+func NewBatchRCK(bh *BatchHeader) *BatchRCK {
+	batch := new(BatchRCK)
+	batch.SetControl(NewBatchControl())
+	batch.SetHeader(bh)
+	return batch
+}
+
+// Validate checks valid NACHA batch rules. Assumes properly parsed records.
+func (batch *BatchRCK) Validate() error {
+	// basic verification of the batch before we validate specific rules.
+	if err := batch.verify(); err != nil {
+		return err
+	}
+
+	// Batch RCK cannot have an addenda record
+	if err := batch.isAddendaCount(0); err != nil {
+		return err
+	}
+
+	// Add type specific validation.
+	if batch.Header.StandardEntryClassCode != "RCK" {
+		msg := fmt.Sprintf(msgBatchSECType, batch.Header.StandardEntryClassCode, "RCK")
+		return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg}
+	}
+
+	// RCK detail entries can only be a debit, ServiceClassCode must allow debits
+	switch batch.Header.ServiceClassCode {
+	case 200, 220, 280:
+		msg := fmt.Sprintf(msgBatchServiceClassCode, batch.Header.ServiceClassCode, "RCK")
+		return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "ServiceClassCode", Msg: msg}
+	}
+
+	// CompanyEntryDescription is required to be REDEPCHECK
+	if batch.Header.CompanyEntryDescription != "REDEPCHECK" {
+		msg := fmt.Sprintf(msgBatchCompanyEntryDescription, batch.Header.CompanyEntryDescription, "RCK")
+		return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "CompanyEntryDescription", Msg: msg}
+	}
+
+	for _, entry := range batch.Entries {
+		// RCK detail entries must be a debit
+		if entry.CreditOrDebit() != "D" {
+			msg := fmt.Sprintf(msgBatchTransactionCodeCredit, entry.TransactionCode)
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "TransactionCode", Msg: msg}
+		}
+
+		// // Amount must be 2,500 or less
+		if entry.Amount > 250000 {
+			msg := fmt.Sprintf(msgBatchAmount, "2,500", "RCK")
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Amount", Msg: msg}
+		}
+
+		// CheckSerialNumber underlying IdentificationNumber, must be defined
+		if entry.IdentificationNumber == "" {
+			msg := fmt.Sprintf(msgBatchCheckSerialNumber, "RCK")
+			return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "CheckSerialNumber", Msg: msg}
+		}
+	}
+	return nil
+}
+
+// Create takes Batch Header and Entries and builds a valid batch
+func (batch *BatchRCK) 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 992b41aa..9203b6b5 100644
--- a/batcher.go
+++ b/batcher.go
@@ -14,7 +14,7 @@ import (
 // * The SEC Code pertains to all items within batch
 //    * Determines format of the entry detail records
 //    * Determines addenda records (required or optional PLUS one or up to 9,999 records)
-//    * Determines rules to follow (return timeframes)
+//    * Determines rules to follow (return time frames)
 // 	  * Some SEC codes require specific data in predetermined fields within the ACH record
 type Batcher interface {
 	GetHeader() *BatchHeader
@@ -47,14 +47,18 @@ var (
 	msgBatchCalculatedControlEquality = "calculated %v is out-of-balance with control %v"
 	msgBatchAscending                 = "%v is less than last %v. Must be in ascending order"
 	// specific messages for error
-	msgBatchOriginatorDNE         = "%v is not “2” for DNE with entry transaction code of 23 or 33"
-	msgBatchTraceNumberNotODFI    = "%v in header does not match entry trace number %v"
-	msgBatchAddendaIndicator      = "is 0 but found addenda record(s)"
-	msgBatchAddendaTraceNumber    = "%v does not match proceeding entry detail trace number %v"
-	msgBatchEntries               = "must have Entry Record(s) to be built"
-	msgBatchAddendaCount          = "%v addendum found where %v is allowed for batch type %v"
-	msgBatchTransactionCodeCredit = "%v a credit is not allowed"
-	msgBatchSECType               = "header SEC type code %v for batch type %v"
-	msgBatchTypeCode              = "%v found in addenda and expecting %v for batch type %v"
-	msgBatchForwardReturn         = "Forward and Return entries found in the same batch"
+	msgBatchCompanyEntryDescription = "Company entry description %v is not valid for batch type %v"
+	msgBatchOriginatorDNE           = "%v is not “2” for DNE with entry transaction code of 23 or 33"
+	msgBatchTraceNumberNotODFI      = "%v in header does not match entry trace number %v"
+	msgBatchAddendaIndicator        = "is 0 but found addenda record(s)"
+	msgBatchAddendaTraceNumber      = "%v does not match proceeding entry detail trace number %v"
+	msgBatchEntries                 = "must have Entry Record(s) to be built"
+	msgBatchAddendaCount            = "%v addendum found where %v is allowed for batch type %v"
+	msgBatchTransactionCodeCredit   = "%v a credit is not allowed"
+	msgBatchSECType                 = "header SEC type code %v for batch type %v"
+	msgBatchTypeCode                = "%v found in addenda and expecting %v for batch type %v"
+	msgBatchServiceClassCode        = "Service Class Code %v is not valid for batch type %v"
+	msgBatchForwardReturn           = "Forward and Return entries found in the same batch"
+	msgBatchAmount                  = "Amount must be less than %v for SEC code %v"
+	msgBatchCheckSerialNumber       = "Check Serial Number is required for SEC code %v"
 )
diff --git a/cmd/readACH/old b/cmd/readACH/old
index 1000c961..3d38a255 100644
--- a/cmd/readACH/old
+++ b/cmd/readACH/old
@@ -1,32 +1,10 @@
-total amount debit: 0 
-total amount credit: 500000000 
-C:\Users\Owner\AppData\Local\Temp\go-build543880490\b001\readACH.test.exe flag redefined: fPath
---- FAIL: TestFileRead (0.00s)
-panic: C:\Users\Owner\AppData\Local\Temp\go-build543880490\b001\readACH.test.exe flag redefined: fPath [recovered]
-	panic: C:\Users\Owner\AppData\Local\Temp\go-build543880490\b001\readACH.test.exe flag redefined: fPath
-
-goroutine 20 [running]:
-testing.tRunner.func1(0xc0420ac2d0)
-	C:/Go/src/testing/testing.go:742 +0x2a4
-panic(0x532f80, 0xc042369d70)
-	C:/Go/src/runtime/panic.go:505 +0x237
-flag.(*FlagSet).Var(0xc042092000, 0x587060, 0xc042369d30, 0x56988e, 0x5, 0x56a171, 0x9)
-	C:/Go/src/flag/flag.go:810 +0x547
-flag.(*FlagSet).StringVar(0xc042092000, 0xc042369d30, 0x56988e, 0x5, 0x56bb10, 0x10, 0x56a171, 0x9)
-	C:/Go/src/flag/flag.go:713 +0x92
-flag.(*FlagSet).String(0xc042092000, 0x56988e, 0x5, 0x56bb10, 0x10, 0x56a171, 0x9, 0xc0420b0000)
-	C:/Go/src/flag/flag.go:726 +0x92
-flag.String(0x56988e, 0x5, 0x56bb10, 0x10, 0x56a171, 0x9, 0x0)
-	C:/Go/src/flag/flag.go:733 +0x70
-github.com/bkmoovio/ach/cmd/readACH.main()
-	C:/Users/Owner/go/src/github.com/bkmoovio/ach/cmd/readACH/main.go:15 +0x7c
-github.com/bkmoovio/ach/cmd/readACH.FileRead(0x588100, 0xc0420ac2d0)
-	C:/Users/Owner/go/src/github.com/bkmoovio/ach/cmd/readACH/main_test.go:20 +0x27
-github.com/bkmoovio/ach/cmd/readACH.TestFileRead(0xc0420ac2d0)
-	C:/Users/Owner/go/src/github.com/bkmoovio/ach/cmd/readACH/main_test.go:6 +0x3e
-testing.tRunner(0xc0420ac2d0, 0x575c00)
-	C:/Go/src/testing/testing.go:777 +0xd7
-created by testing.(*T).Run
-	C:/Go/src/testing/testing.go:824 +0x2e7
-exit status 2
-FAIL	github.com/bkmoovio/ach/cmd/readACH	4.474s
+goos: windows
+goarch: amd64
+pkg: github.com/bkmoovio/ach/cmd/readACH
+BenchmarkTestFileRead-8   	      50	  33572202 ns/op	 3102486 B/op	  134694 allocs/op
+BenchmarkTestFileRead-8   	      50	  33500368 ns/op	 3102480 B/op	  134694 allocs/op
+BenchmarkTestFileRead-8   	      50	  33610982 ns/op	 3102478 B/op	  134694 allocs/op
+BenchmarkTestFileRead-8   	      50	  33380088 ns/op	 3102479 B/op	  134694 allocs/op
+BenchmarkTestFileRead-8   	      50	  32952968 ns/op	 3102482 B/op	  134694 allocs/op
+PASS
+ok  	github.com/bkmoovio/ach/cmd/readACH	11.783s
diff --git a/cmd/writeACH/old b/cmd/writeACH/old
index 95eb3485..822b80b0 100644
--- a/cmd/writeACH/old
+++ b/cmd/writeACH/old
@@ -1,30 +1,10 @@
-C:\Users\Owner\AppData\Local\Temp\go-build176990002\b001\writeACH.test.exe flag redefined: fPath
---- FAIL: TestFileCreate (0.00s)
-panic: C:\Users\Owner\AppData\Local\Temp\go-build176990002\b001\writeACH.test.exe flag redefined: fPath [recovered]
-	panic: C:\Users\Owner\AppData\Local\Temp\go-build176990002\b001\writeACH.test.exe flag redefined: fPath
-
-goroutine 50 [running]:
-testing.tRunner.func1(0xc0420fe2d0)
-	C:/Go/src/testing/testing.go:742 +0x2a4
-panic(0x556400, 0xc042226270)
-	C:/Go/src/runtime/panic.go:505 +0x237
-flag.(*FlagSet).Var(0xc042092000, 0x5b1a40, 0xc042226230, 0x591fa0, 0x5, 0x59292e, 0x9)
-	C:/Go/src/flag/flag.go:810 +0x547
-flag.(*FlagSet).StringVar(0xc042092000, 0xc042226230, 0x591fa0, 0x5, 0x0, 0x0, 0x59292e, 0x9)
-	C:/Go/src/flag/flag.go:713 +0x92
-flag.(*FlagSet).String(0xc042092000, 0x591fa0, 0x5, 0x0, 0x0, 0x59292e, 0x9, 0x0)
-	C:/Go/src/flag/flag.go:726 +0x92
-flag.String(0x591fa0, 0x5, 0x0, 0x0, 0x59292e, 0x9, 0x0)
-	C:/Go/src/flag/flag.go:733 +0x70
-github.com/bkmoovio/ach/cmd/writeACH.main()
-	C:/Users/Owner/go/src/github.com/bkmoovio/ach/cmd/writeACH/main.go:18 +0x6f
-github.com/bkmoovio/ach/cmd/writeACH.FileCreate(0x5b3200, 0xc0420fe2d0)
-	C:/Users/Owner/go/src/github.com/bkmoovio/ach/cmd/writeACH/main_test.go:23 +0x27
-github.com/bkmoovio/ach/cmd/writeACH.TestFileCreate(0xc0420fe2d0)
-	C:/Users/Owner/go/src/github.com/bkmoovio/ach/cmd/writeACH/main_test.go:10 +0x3e
-testing.tRunner(0xc0420fe2d0, 0x59ef18)
-	C:/Go/src/testing/testing.go:777 +0xd7
-created by testing.(*T).Run
-	C:/Go/src/testing/testing.go:824 +0x2e7
-exit status 2
-FAIL	github.com/bkmoovio/ach/cmd/writeACH	0.579s
+goos: windows
+goarch: amd64
+pkg: github.com/bkmoovio/ach/cmd/writeACH
+BenchmarkTestFileWrite-8   	     100	  16101905 ns/op	 2325178 B/op	  119293 allocs/op
+BenchmarkTestFileWrite-8   	     100	  16306688 ns/op	 2325160 B/op	  119293 allocs/op
+BenchmarkTestFileWrite-8   	     100	  20868257 ns/op	 2325161 B/op	  119293 allocs/op
+BenchmarkTestFileWrite-8   	     100	  16199637 ns/op	 2325160 B/op	  119293 allocs/op
+BenchmarkTestFileWrite-8   	     100	  16090635 ns/op	 2325160 B/op	  119293 allocs/op
+PASS
+ok  	github.com/bkmoovio/ach/cmd/writeACH	10.120s
diff --git a/entryDetail.go b/entryDetail.go
index 01e50555..abb3cffe 100644
--- a/entryDetail.go
+++ b/entryDetail.go
@@ -1,4 +1,4 @@
-// Copyright 2017 The ACH Authors
+// 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.
 
@@ -270,6 +270,18 @@ func (ed *EntryDetail) IdentificationNumberField() string {
 	return ed.alphaField(ed.IdentificationNumber, 15)
 }
 
+// CheckSerialNumberField is used in RCK, ARC, BOC files but returns
+// a space padded string of the underlying IdentificationNumber field
+func (ed *EntryDetail) CheckSerialNumberField() string {
+	return ed.alphaField(ed.IdentificationNumber, 15)
+}
+
+// SetCheckSerialNumber setter for RCK, ARC, BOC CheckSerialNumber
+// which is underlying IdentificationNumber
+func (ed *EntryDetail) SetCheckSerialNumber(s string) {
+	ed.IdentificationNumber = s
+}
+
 // IndividualNameField returns a space padded string of IndividualName
 func (ed *EntryDetail) IndividualNameField() string {
 	return ed.alphaField(ed.IndividualName, 22)
@@ -280,7 +292,7 @@ func (ed *EntryDetail) ReceivingCompanyField() string {
 	return ed.IndividualNameField()
 }
 
-// SetReceivingCompany setter for CCD receiving company individual name
+// SetReceivingCompany setter for CCD ReceivingCompany which is underlying IndividualName
 func (ed *EntryDetail) SetReceivingCompany(s string) {
 	ed.IndividualName = s
 }
@@ -290,7 +302,7 @@ func (ed *EntryDetail) DiscretionaryDataField() string {
 	return ed.alphaField(ed.DiscretionaryData, 2)
 }
 
-// PaymentTypeField returns the discretionary data field used in WEB batch files
+// PaymentTypeField returns the DiscretionaryData field used in WEB batch files
 func (ed *EntryDetail) PaymentTypeField() string {
 	// because DiscretionaryData can be changed outside of PaymentType we reset the value for safety
 	ed.SetPaymentType(ed.DiscretionaryData)
@@ -307,7 +319,7 @@ func (ed *EntryDetail) SetPaymentType(t string) {
 	}
 }
 
-// TraceNumberField returns a zero padded traceNumber string
+// TraceNumberField returns a zero padded TraceNumber string
 func (ed *EntryDetail) TraceNumberField() string {
 	return ed.numericField(ed.TraceNumber, 15)
 }
@@ -315,11 +327,11 @@ func (ed *EntryDetail) TraceNumberField() string {
 // CreditOrDebit returns a "C" for credit or "D" for debit based on the entry TransactionCode
 func (ed *EntryDetail) CreditOrDebit() string {
 	tc := strconv.Itoa(ed.TransactionCode)
-	// take the second number in the Transaction code
+	// take the second number in the TransactionCode
 	switch tc[1:2] {
-	case "1", "2", "3":
+	case "1", "2", "3", "4":
 		return "C"
-	case "6", "7", "8":
+	case "5", "6", "7", "8", "9":
 		return "D"
 	}
 	return ""


Test patch:
diff --git a/BatchARC_test.go b/BatchARC_test.go
new file mode 100644
index 00000000..d1b372ed
--- /dev/null
+++ b/BatchARC_test.go
@@ -0,0 +1,352 @@
+// 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 "testing"
+
+// mockBatchARCHeader creates a BatchARC BatchHeader
+func mockBatchARCHeader() *BatchHeader {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 225
+	bh.StandardEntryClassCode = "ARC"
+	bh.CompanyName = "Payee Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "ARC"
+	bh.ODFIIdentification = "12104288"
+	return bh
+}
+
+// mockARCEntryDetail creates a BatchARC EntryDetail
+func mockARCEntryDetail() *EntryDetail {
+	entry := NewEntryDetail()
+	entry.TransactionCode = 27
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.SetCheckSerialNumber("123456789")
+	entry.SetReceivingCompany("ABC Company")
+	entry.SetTraceNumber(mockBatchARCHeader().ODFIIdentification, 123)
+	entry.Category = CategoryForward
+	return entry
+}
+
+// mockBatchARC creates a BatchARC
+func mockBatchARC() *BatchARC {
+	mockBatch := NewBatchARC(mockBatchARCHeader())
+	mockBatch.AddEntry(mockARCEntryDetail())
+	if err := mockBatch.Create(); err != nil {
+		panic(err)
+	}
+	return mockBatch
+}
+
+// mockBatchARCHeaderCredit creates a BatchARC BatchHeader
+func mockBatchARCHeaderCredit() *BatchHeader {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 225
+	bh.StandardEntryClassCode = "ARC"
+	bh.CompanyName = "Payee Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "ARC"
+	bh.ODFIIdentification = "12104288"
+	return bh
+}
+
+// mockARCEntryDetailCredit creates a ARC EntryDetail with a credit entry
+func mockARCEntryDetailCredit() *EntryDetail {
+	entry := NewEntryDetail()
+	entry.TransactionCode = 22
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.SetCheckSerialNumber("123456789")
+	entry.SetReceivingCompany("ABC Company")
+	entry.SetTraceNumber(mockBatchARCHeader().ODFIIdentification, 123)
+	entry.Category = CategoryForward
+	return entry
+}
+
+// mockBatchARCCredit creates a BatchARC with a Credit entry
+func mockBatchARCCredit() *BatchARC {
+	mockBatch := NewBatchARC(mockBatchARCHeaderCredit())
+	mockBatch.AddEntry(mockARCEntryDetailCredit())
+	return mockBatch
+}
+
+// testBatchARCHeader creates a BatchARC BatchHeader
+func testBatchARCHeader(t testing.TB) {
+	batch, _ := NewBatch(mockBatchARCHeader())
+	err, ok := batch.(*BatchARC)
+	if !ok {
+		t.Errorf("Expecting BatchARC got %T", err)
+	}
+}
+
+// TestBatchARCHeader tests validating BatchARC BatchHeader
+func TestBatchARCHeader(t *testing.T) {
+	testBatchARCHeader(t)
+}
+
+// BenchmarkBatchARCHeader benchmarks validating BatchARC BatchHeader
+func BenchmarkBatchARCHeader(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCHeader(b)
+	}
+}
+
+// testBatchARCCreate validates BatchARC create
+func testBatchARCCreate(t testing.TB) {
+	mockBatch := mockBatchARC()
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		t.Errorf("%T: %s", err, err)
+	}
+}
+
+// TestBatchARCCreate tests validating BatchARC create
+func TestBatchARCCreate(t *testing.T) {
+	testBatchARCCreate(t)
+}
+
+// BenchmarkBatchARCCreate benchmarks validating BatchARC create
+func BenchmarkBatchARCCreate(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCCreate(b)
+	}
+}
+
+// testBatchARCStandardEntryClassCode validates BatchARC create for an invalid StandardEntryClassCode
+func testBatchARCStandardEntryClassCode(t testing.TB) {
+	mockBatch := mockBatchARC()
+	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)
+		}
+	}
+}
+
+// TestBatchARCStandardEntryClassCode tests validating BatchARC create for an invalid StandardEntryClassCode
+func TestBatchARCStandardEntryClassCode(t *testing.T) {
+	testBatchARCStandardEntryClassCode(t)
+}
+
+// BenchmarkBatchARCStandardEntryClassCode benchmarks validating BatchARC create for an invalid StandardEntryClassCode
+func BenchmarkBatchARCStandardEntryClassCode(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCStandardEntryClassCode(b)
+	}
+}
+
+// testBatchARCServiceClass200 validates BatchARC create for an invalid ServiceClassCode 200
+func testBatchARCServiceClass200(t testing.TB) {
+	mockBatch := mockBatchARC()
+	mockBatch.Header.ServiceClassCode = 200
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchARCServiceClass200 tests validating BatchARC create for an invalid ServiceClassCode 200
+func TestBatchARCServiceClass200(t *testing.T) {
+	testBatchARCServiceClass200(t)
+}
+
+// BenchmarkBatchARCServiceClass200 benchmarks validating BatchARC create for an invalid ServiceClassCode 200
+func BenchmarkBatchARCServiceClass200(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCServiceClass200(b)
+	}
+}
+
+// testBatchARCServiceClass220 validates BatchARC create for an invalid ServiceClassCode 220
+func testBatchARCServiceClass220(t testing.TB) {
+	mockBatch := mockBatchARC()
+	mockBatch.Header.ServiceClassCode = 220
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchARCServiceClass220 tests validating BatchARC create for an invalid ServiceClassCode 220
+func TestBatchARCServiceClass220(t *testing.T) {
+	testBatchARCServiceClass220(t)
+}
+
+// BenchmarkBatchARCServiceClass220 benchmarks validating BatchARC create for an invalid ServiceClassCode 220
+func BenchmarkBatchARCServiceClass220(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCServiceClass220(b)
+	}
+}
+
+// testBatchARCServiceClass280 validates BatchARC create for an invalid ServiceClassCode 280
+func testBatchARCServiceClass280(t testing.TB) {
+	mockBatch := mockBatchARC()
+	mockBatch.Header.ServiceClassCode = 280
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchARCServiceClass280 tests validating BatchARC create for an invalid ServiceClassCode 280
+func TestBatchARCServiceClass280(t *testing.T) {
+	testBatchARCServiceClass280(t)
+}
+
+// BenchmarkBatchARCServiceClass280 benchmarks validating BatchARC create for an invalid ServiceClassCode 280
+func BenchmarkBatchARCServiceClass280(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCServiceClass280(b)
+	}
+}
+
+// testBatchARCAmount validates BatchARC create for an invalid Amount
+func testBatchARCAmount(t testing.TB) {
+	mockBatch := mockBatchARC()
+	mockBatch.Entries[0].Amount = 2600000
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Amount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchARCAmount validates BatchARC create for an invalid Amount
+func TestBatchARCAmount(t *testing.T) {
+	testBatchARCAmount(t)
+}
+
+// BenchmarkBatchARCAmount validates BatchARC create for an invalid Amount
+func BenchmarkBatchARCAmount(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCAmount(b)
+	}
+}
+
+// testBatchARCCheckSerialNumber validates BatchARC CheckSerialNumber / IdentificationNumber is a mandatory field
+func testBatchARCCheckSerialNumber(t testing.TB) {
+	mockBatch := mockBatchARC()
+	// modify CheckSerialNumber / IdentificationNumber to nothing
+	mockBatch.GetEntries()[0].SetCheckSerialNumber("")
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "CheckSerialNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchARCCheckSerialNumber  tests validating BatchARC
+// CheckSerialNumber / IdentificationNumber is a mandatory field
+func TestBatchARCCheckSerialNumber(t *testing.T) {
+	testBatchARCCheckSerialNumber(t)
+}
+
+// BenchmarkBatchARCCheckSerialNumber benchmarks validating BatchARC
+// CheckSerialNumber / IdentificationNumber is a mandatory field
+func BenchmarkBatchARCCheckSerialNumber(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCCheckSerialNumber(b)
+	}
+}
+
+// testBatchARCTransactionCode validates BatchARC TransactionCode is not a credit
+func testBatchARCTransactionCode(t testing.TB) {
+	mockBatch := mockBatchARCCredit()
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "TransactionCode" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchARCTransactionCode tests validating BatchARC TransactionCode is not a credit
+func TestBatchARCTransactionCode(t *testing.T) {
+	testBatchARCTransactionCode(t)
+}
+
+// BenchmarkBatchARCTransactionCode benchmarks validating BatchARC TransactionCode is not a credit
+func BenchmarkBatchARCTransactionCode(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCTransactionCode(b)
+	}
+}
+
+// testBatchARCAddendaCount validates BatchARC Addenda count
+func testBatchARCAddendaCount(t testing.TB) {
+	mockBatch := mockBatchARC()
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "AddendaCount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchARCAddendaCount tests validating BatchARC Addenda count
+func TestBatchARCAddendaCount(t *testing.T) {
+	testBatchARCAddendaCount(t)
+}
+
+// BenchmarkBatchARCAddendaCount benchmarks validating BatchARC Addenda count
+func BenchmarkBatchARCAddendaCount(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchARCAddendaCount(b)
+	}
+}
diff --git a/batchBOC_test.go b/batchBOC_test.go
new file mode 100644
index 00000000..2fc05a3e
--- /dev/null
+++ b/batchBOC_test.go
@@ -0,0 +1,351 @@
+// 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 "testing"
+
+// mockBatchBOCHeader creates a BatchBOC BatchHeader
+func mockBatchBOCHeader() *BatchHeader {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 225
+	bh.StandardEntryClassCode = "BOC"
+	bh.CompanyName = "Company Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "BOC"
+	bh.ODFIIdentification = "12104288"
+	return bh
+}
+
+// mockBOCEntryDetail creates a BatchBOC EntryDetail
+func mockBOCEntryDetail() *EntryDetail {
+	entry := NewEntryDetail()
+	entry.TransactionCode = 27
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.SetCheckSerialNumber("123456789")
+	entry.SetReceivingCompany("ABC Company")
+	entry.SetTraceNumber(mockBatchBOCHeader().ODFIIdentification, 123)
+	entry.Category = CategoryForward
+	return entry
+}
+
+// mockBatchBOC creates a BatchBOC
+func mockBatchBOC() *BatchBOC {
+	mockBatch := NewBatchBOC(mockBatchBOCHeader())
+	mockBatch.AddEntry(mockBOCEntryDetail())
+	if err := mockBatch.Create(); err != nil {
+		panic(err)
+	}
+	return mockBatch
+}
+
+// mockBatchBOCHeaderCredit creates a BatchBOC BatchHeader
+func mockBatchBOCHeaderCredit() *BatchHeader {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 225
+	bh.StandardEntryClassCode = "BOC"
+	bh.CompanyName = "Company Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "REDEPCHECK"
+	bh.ODFIIdentification = "12104288"
+	return bh
+}
+
+// mockBOCEntryDetailCredit creates a BatchBOC EntryDetail with a credit
+func mockBOCEntryDetailCredit() *EntryDetail {
+	entry := NewEntryDetail()
+	entry.TransactionCode = 22
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 25000
+	entry.SetCheckSerialNumber("123456789")
+	entry.SetReceivingCompany("ABC Company")
+	entry.SetTraceNumber(mockBatchBOCHeader().ODFIIdentification, 123)
+	entry.Category = CategoryForward
+	return entry
+}
+
+// mockBatchBOCCredit creates a BatchBOC with a Credit entry
+func mockBatchBOCCredit() *BatchBOC {
+	mockBatch := NewBatchBOC(mockBatchBOCHeaderCredit())
+	mockBatch.AddEntry(mockBOCEntryDetailCredit())
+	return mockBatch
+}
+
+// testBatchBOCHeader creates a BatchBOC BatchHeader
+func testBatchBOCHeader(t testing.TB) {
+	batch, _ := NewBatch(mockBatchBOCHeader())
+	err, ok := batch.(*BatchBOC)
+	if !ok {
+		t.Errorf("Expecting BatchBOC got %T", err)
+	}
+}
+
+// TestBatchBOCHeader tests validating BatchBOC BatchHeader
+func TestBatchBOCHeader(t *testing.T) {
+	testBatchBOCHeader(t)
+}
+
+// BenchmarkBatchBOCHeader benchmarks validating BatchBOC BatchHeader
+func BenchmarkBatchBOCHeader(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCHeader(b)
+	}
+}
+
+// testBatchBOCCreate validates BatchBOC create
+func testBatchBOCCreate(t testing.TB) {
+	mockBatch := mockBatchBOC()
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		t.Errorf("%T: %s", err, err)
+	}
+}
+
+// TestBatchBOCCreate tests validating BatchBOC create
+func TestBatchBOCCreate(t *testing.T) {
+	testBatchBOCCreate(t)
+}
+
+// BenchmarkBatchBOCCreate benchmarks validating BatchBOC create
+func BenchmarkBatchBOCCreate(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCCreate(b)
+	}
+}
+
+// testBatchBOCStandardEntryClassCode validates BatchBOC create for an invalid StandardEntryClassCode
+func testBatchBOCStandardEntryClassCode(t testing.TB) {
+	mockBatch := mockBatchBOC()
+	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)
+		}
+	}
+}
+
+// TestBatchBOCStandardEntryClassCode tests validating BatchBOC create for an invalid StandardEntryClassCode
+func TestBatchBOCStandardEntryClassCode(t *testing.T) {
+	testBatchBOCStandardEntryClassCode(t)
+}
+
+// BenchmarkBatchBOCStandardEntryClassCode benchmarks validating BatchBOC create for an invalid StandardEntryClassCode
+func BenchmarkBatchBOCStandardEntryClassCode(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCStandardEntryClassCode(b)
+	}
+}
+
+// testBatchBOCServiceClass200 validates BatchBOC create for an invalid ServiceClassCode 200
+func testBatchBOCServiceClass200(t testing.TB) {
+	mockBatch := mockBatchBOC()
+	mockBatch.Header.ServiceClassCode = 200
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchBOCServiceClass200 tests validating BatchBOC create for an invalid ServiceClassCode 200
+func TestBatchBOCServiceClass200(t *testing.T) {
+	testBatchBOCServiceClass200(t)
+}
+
+// BenchmarkBatchBOCServiceClass200 benchmarks validating BatchBOC create for an invalid ServiceClassCode 200
+func BenchmarkBatchBOCServiceClass200(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCServiceClass200(b)
+	}
+}
+
+// testBatchBOCServiceClass220 validates BatchBOC create for an invalid ServiceClassCode 220
+func testBatchBOCServiceClass220(t testing.TB) {
+	mockBatch := mockBatchBOC()
+	mockBatch.Header.ServiceClassCode = 220
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchBOCServiceClass220 tests validating BatchBOC create for an invalid ServiceClassCode 220
+func TestBatchBOCServiceClass220(t *testing.T) {
+	testBatchBOCServiceClass220(t)
+}
+
+// BenchmarkBatchBOCServiceClass220 benchmarks validating BatchBOC create for an invalid ServiceClassCode 220
+func BenchmarkBatchBOCServiceClass220(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCServiceClass220(b)
+	}
+}
+
+// testBatchBOCServiceClass280 validates BatchBOC create for an invalid ServiceClassCode 280
+func testBatchBOCServiceClass280(t testing.TB) {
+	mockBatch := mockBatchBOC()
+	mockBatch.Header.ServiceClassCode = 280
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchBOCServiceClass280 tests validating BatchBOC create for an invalid ServiceClassCode 280
+func TestBatchBOCServiceClass280(t *testing.T) {
+	testBatchBOCServiceClass280(t)
+}
+
+// BenchmarkBatchBOCServiceClass280 benchmarks validating BatchBOC create for an invalid ServiceClassCode 280
+func BenchmarkBatchBOCServiceClass280(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCServiceClass280(b)
+	}
+}
+
+// testBatchBOCAmount validates BatchBOC create for an invalid Amount
+func testBatchBOCAmount(t testing.TB) {
+	mockBatch := mockBatchBOC()
+	mockBatch.Entries[0].Amount = 2500001
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Amount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchBOCAmount validates BatchBOC create for an invalid Amount
+func TestBatchBOCAmount(t *testing.T) {
+	testBatchBOCAmount(t)
+}
+
+// BenchmarkBatchBOCAmount validates BatchBOC create for an invalid Amount
+func BenchmarkBatchBOCAmount(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCAmount(b)
+	}
+}
+
+// testBatchBOCCheckSerialNumber validates BatchBOC CheckSerialNumber / IdentificationNumber is a mandatory field
+func testBatchBOCCheckSerialNumber(t testing.TB) {
+	mockBatch := mockBatchBOC()
+	// modify CheckSerialNumber / IdentificationNumber to empty string
+	mockBatch.GetEntries()[0].SetCheckSerialNumber("")
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "CheckSerialNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchBOCCheckSerialNumber  tests validating BatchBOC CheckSerialNumber / IdentificationNumber is a mandatory field
+func TestBatchBOCCheckSerialNumber(t *testing.T) {
+	testBatchBOCCheckSerialNumber(t)
+}
+
+// BenchmarkBatchBOCCheckSerialNumber benchmarks validating BatchBOC
+// CheckSerialNumber / IdentificationNumber is a mandatory field
+func BenchmarkBatchBOCCheckSerialNumber(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCCheckSerialNumber(b)
+	}
+}
+
+// testBatchBOCTransactionCode validates BatchBOC TransactionCode is not a credit
+func testBatchBOCTransactionCode(t testing.TB) {
+	mockBatch := mockBatchBOCCredit()
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "TransactionCode" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchBOCTransactionCode tests validating BatchBOC TransactionCode is not a credit
+func TestBatchBOCTransactionCode(t *testing.T) {
+	testBatchBOCTransactionCode(t)
+}
+
+// BenchmarkBatchBOCTransactionCode benchmarks validating BatchBOC TransactionCode is not a credit
+func BenchmarkBatchBOCTransactionCode(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCTransactionCode(b)
+	}
+}
+
+// testBatchBOCAddendaCount validates BatchBOC Addenda count
+func testBatchBOCAddendaCount(t testing.TB) {
+	mockBatch := mockBatchBOC()
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "AddendaCount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchBOCAddendaCount tests validating BatchBOC Addenda count
+func TestBatchBOCAddendaCount(t *testing.T) {
+	testBatchBOCAddendaCount(t)
+}
+
+// BenchmarkBatchBOCAddendaCount benchmarks validating BatchBOC Addenda count
+func BenchmarkBatchBOCAddendaCount(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchBOCAddendaCount(b)
+	}
+}
diff --git a/batchRCK_test.go b/batchRCK_test.go
new file mode 100644
index 00000000..47296555
--- /dev/null
+++ b/batchRCK_test.go
@@ -0,0 +1,381 @@
+// 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 "testing"
+
+// mockBatchRCKHeader creates a BatchRCK BatchHeader
+func mockBatchRCKHeader() *BatchHeader {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 225
+	bh.StandardEntryClassCode = "RCK"
+	bh.CompanyName = "Company Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "REDEPCHECK"
+	bh.ODFIIdentification = "12104288"
+	return bh
+}
+
+// mockRCKEntryDetail creates a BatchRCK EntryDetail
+func mockRCKEntryDetail() *EntryDetail {
+	entry := NewEntryDetail()
+	entry.TransactionCode = 27
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 2400
+	entry.SetCheckSerialNumber("123456789")
+	entry.IndividualName = "Wade Arnold"
+	entry.SetTraceNumber(mockBatchRCKHeader().ODFIIdentification, 123)
+	entry.Category = CategoryForward
+	return entry
+}
+
+// mockBatchRCK creates a BatchRCK
+func mockBatchRCK() *BatchRCK {
+	mockBatch := NewBatchRCK(mockBatchRCKHeader())
+	mockBatch.AddEntry(mockRCKEntryDetail())
+	if err := mockBatch.Create(); err != nil {
+		panic(err)
+	}
+	return mockBatch
+}
+
+// mockBatchRCKHeaderCredit creates a BatchRCK BatchHeader
+func mockBatchRCKHeaderCredit() *BatchHeader {
+	bh := NewBatchHeader()
+	bh.ServiceClassCode = 225
+	bh.StandardEntryClassCode = "RCK"
+	bh.CompanyName = "Company Name"
+	bh.CompanyIdentification = "121042882"
+	bh.CompanyEntryDescription = "REDEPCHECK"
+	bh.ODFIIdentification = "12104288"
+	return bh
+}
+
+// mockRCKEntryDetailCredit creates a BatchRCK EntryDetail with a credit entry
+func mockRCKEntryDetailCredit() *EntryDetail {
+	entry := NewEntryDetail()
+	entry.TransactionCode = 22
+	entry.SetRDFI("231380104")
+	entry.DFIAccountNumber = "744-5678-99"
+	entry.Amount = 2400
+	entry.SetCheckSerialNumber("123456789")
+	entry.IndividualName = "Wade Arnold"
+	entry.SetTraceNumber(mockBatchRCKHeader().ODFIIdentification, 123)
+	entry.Category = CategoryForward
+	return entry
+}
+
+// mockBatchRCKCredit creates a BatchRCK with a credit entry
+func mockBatchRCKCredit() *BatchRCK {
+	mockBatch := NewBatchRCK(mockBatchRCKHeaderCredit())
+	mockBatch.AddEntry(mockRCKEntryDetailCredit())
+	return mockBatch
+}
+
+// testBatchRCKHeader creates a BatchRCK BatchHeader
+func testBatchRCKHeader(t testing.TB) {
+	batch, _ := NewBatch(mockBatchRCKHeader())
+	err, ok := batch.(*BatchRCK)
+	if !ok {
+		t.Errorf("Expecting BatchRCK got %T", err)
+	}
+}
+
+// TestBatchRCKHeader tests validating BatchRCK BatchHeader
+func TestBatchRCKHeader(t *testing.T) {
+	testBatchRCKHeader(t)
+}
+
+// BenchmarkBatchRCKHeader benchmarks validating BatchRCK BatchHeader
+func BenchmarkBatchRCKHeader(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKHeader(b)
+	}
+}
+
+// testBatchRCKCreate validates BatchRCK create
+func testBatchRCKCreate(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		t.Errorf("%T: %s", err, err)
+	}
+}
+
+// TestBatchRCKCreate tests validating BatchRCK create
+func TestBatchRCKCreate(t *testing.T) {
+	testBatchRCKCreate(t)
+}
+
+// BenchmarkBatchRCKCreate benchmarks validating BatchRCK create
+func BenchmarkBatchRCKCreate(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKCreate(b)
+	}
+}
+
+// testBatchRCKStandardEntryClassCode validates BatchRCK create for an invalid StandardEntryClassCode
+func testBatchRCKStandardEntryClassCode(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	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)
+		}
+	}
+}
+
+// TestBatchRCKStandardEntryClassCode tests validating BatchRCK create for an invalid StandardEntryClassCode
+func TestBatchRCKStandardEntryClassCode(t *testing.T) {
+	testBatchRCKStandardEntryClassCode(t)
+}
+
+// BenchmarkBatchRCKStandardEntryClassCode benchmarks validating BatchRCK create for an invalid StandardEntryClassCode
+func BenchmarkBatchRCKStandardEntryClassCode(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKStandardEntryClassCode(b)
+	}
+}
+
+// testBatchRCKServiceClass200 validates BatchRCK create for an invalid ServiceClassCode 200
+func testBatchRCKServiceClass200(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	mockBatch.Header.ServiceClassCode = 200
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchRCKServiceClass200 tests validating BatchRCK create for an invalid ServiceClassCode 200
+func TestBatchRCKServiceClass200(t *testing.T) {
+	testBatchRCKServiceClass200(t)
+}
+
+// BenchmarkBatchRCKServiceClass200 benchmarks validating BatchRCK create for an invalid ServiceClassCode 200
+func BenchmarkBatchRCKServiceClass200(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKServiceClass200(b)
+	}
+}
+
+// testBatchRCKServiceClass220 validates BatchRCK create for an invalid ServiceClassCode 220
+func testBatchRCKServiceClass220(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	mockBatch.Header.ServiceClassCode = 220
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchRCKServiceClass220 tests validating BatchRCK create for an invalid ServiceClassCode 220
+func TestBatchRCKServiceClass220(t *testing.T) {
+	testBatchRCKServiceClass220(t)
+}
+
+// BenchmarkBatchRCKServiceClass220 benchmarks validating BatchRCK create for an invalid ServiceClassCode 220
+func BenchmarkBatchRCKServiceClass220(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKServiceClass220(b)
+	}
+}
+
+// testBatchRCKServiceClass280 validates BatchRCK create for an invalid ServiceClassCode 280
+func testBatchRCKServiceClass280(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	mockBatch.Header.ServiceClassCode = 280
+	mockBatch.Create()
+	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)
+		}
+	}
+}
+
+// TestBatchRCKServiceClass280 tests validating BatchRCK create for an invalid ServiceClassCode 280
+func TestBatchRCKServiceClass280(t *testing.T) {
+	testBatchRCKServiceClass280(t)
+}
+
+// BenchmarkBatchRCKServiceClass280 benchmarks validating BatchRCK create for an invalid ServiceClassCode 280
+func BenchmarkBatchRCKServiceClass280(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKServiceClass280(b)
+	}
+}
+
+// testBatchRCKCompanyEntryDescription validates BatchRCK create for an invalid CompanyEntryDescription
+func testBatchRCKCompanyEntryDescription(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	mockBatch.Header.CompanyEntryDescription = "XYZ975"
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "CompanyEntryDescription" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchRCKCompanyEntryDescription validates BatchRCK create for an invalid CompanyEntryDescription
+func TestBatchRCKCompanyEntryDescription(t *testing.T) {
+	testBatchRCKCompanyEntryDescription(t)
+}
+
+// BenchmarkBatchRCKCompanyEntryDescription validates BatchRCK create for an invalid CompanyEntryDescription
+func BenchmarkBatchRCKCompanyEntryDescription(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKCompanyEntryDescription(b)
+	}
+}
+
+// testBatchRCKAmount validates BatchRCK create for an invalid Amount
+func testBatchRCKAmount(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	mockBatch.Entries[0].Amount = 250001
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "Amount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchRCKAmount validates BatchRCK create for an invalid Amount
+func TestBatchRCKAmount(t *testing.T) {
+	testBatchRCKAmount(t)
+}
+
+// BenchmarkBatchRCKAmount validates BatchRCK create for an invalid Amount
+func BenchmarkBatchRCKAmount(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKAmount(b)
+	}
+}
+
+// testBatchRCKCheckSerialNumber validates BatchRCK CheckSerialNumber / IdentificationNumber is a mandatory field
+func testBatchRCKCheckSerialNumber(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	// modify CheckSerialNumber / IdentificationNumber to empty string
+	mockBatch.GetEntries()[0].SetCheckSerialNumber("")
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "CheckSerialNumber" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchRCKCheckSerialNumber tests validating BatchRCK
+// CheckSerialNumber / IdentificationNumber is a mandatory field
+func TestBatchRCKCheckSerialNumber(t *testing.T) {
+	testBatchRCKCheckSerialNumber(t)
+}
+
+// BenchmarkBatchRCKCheckSerialNumber benchmarks validating BatchRCK
+// CheckSerialNumber / IdentificationNumber is a mandatory field
+func BenchmarkBatchRCKCheckSerialNumber(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKCheckSerialNumber(b)
+	}
+}
+
+// testBatchRCKTransactionCode validates BatchRCK TransactionCode is not a credit
+func testBatchRCKTransactionCode(t testing.TB) {
+	mockBatch := mockBatchRCKCredit()
+	if err := mockBatch.Create(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "TransactionCode" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchRCKTransactionCode tests validating BatchRCK TransactionCode is not a credit
+func TestBatchRCKTransactionCode(t *testing.T) {
+	testBatchRCKTransactionCode(t)
+}
+
+// BenchmarkBatchRCKTransactionCode benchmarks validating BatchRCK TransactionCode is not a credit
+func BenchmarkBatchRCKTransactionCode(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKTransactionCode(b)
+	}
+}
+
+// testBatchRCKAddendaCount validates BatchRCK addenda count
+func testBatchRCKAddendaCount(t testing.TB) {
+	mockBatch := mockBatchRCK()
+	mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
+	mockBatch.Create()
+	if err := mockBatch.Validate(); err != nil {
+		if e, ok := err.(*BatchError); ok {
+			if e.FieldName != "AddendaCount" {
+				t.Errorf("%T: %s", err, err)
+			}
+		} else {
+			t.Errorf("%T: %s", err, err)
+		}
+	}
+}
+
+// TestBatchRCKAddendaCount tests validating BatchRCK addenda count
+func TestBatchRCKAddendaCount(t *testing.T) {
+	testBatchRCKAddendaCount(t)
+}
+
+// BenchmarkBatchRCKAddendaCount benchmarks validating BatchRCK addenda count
+func BenchmarkBatchRCKAddendaCount(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		testBatchRCKAddendaCount(b)
+	}
+}
diff --git a/batch_test.go b/batch_test.go
index 18be510d..47e9b14c 100644
--- a/batch_test.go
+++ b/batch_test.go
@@ -20,7 +20,7 @@ func mockBatch() *batch {
 	return mockBatch
 }
 
-// Batch with mismatched Trace Number ODFI
+// Batch with mismatched TraceNumber ODFI
 func mockBatchInvalidTraceNumberODFI() *batch {
 	mockBatch := &batch{}
 	mockBatch.SetHeader(mockBatchHeader())
@@ -28,7 +28,7 @@ func mockBatchInvalidTraceNumberODFI() *batch {
 	return mockBatch
 }
 
-// Entry Detail with mismatched Trace Number ODFI
+// EntryDetail with mismatched TraceNumber ODFI
 func mockEntryDetailInvalidTraceNumberODFI() *EntryDetail {
 	entry := NewEntryDetail()
 	entry.TransactionCode = 22
@@ -49,7 +49,7 @@ func mockBatchNoEntry() *batch {
 	return mockBatch
 }
 
-// Invalid SEC CODE Batch Header
+// Invalid SEC CODE BatchHeader
 func mockBatchInvalidSECHeader() *BatchHeader {
 	bh := NewBatchHeader()
 	bh.ServiceClassCode = 220
diff --git a/cmd/readACH/main_test.go b/cmd/readACH/main_test.go
index 9e25eab8..68bde608 100644
--- a/cmd/readACH/main_test.go
+++ b/cmd/readACH/main_test.go
@@ -6,13 +6,13 @@ func TestFileRead(t *testing.T) {
 	testFileRead(t)
 }
 
-//BenchmarkTestFileCreate benchmarks creating an ACH File
+/*//BenchmarkTestFileCreate benchmarks creating an ACH File
 func BenchmarkTestFileRead(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
 		testFileRead(b)
 	}
-}
+}*/
 
 // FileCreate creates an ACH File
 func testFileRead(t testing.TB) {
diff --git a/cmd/writeACH/main_test.go b/cmd/writeACH/main_test.go
index 3e6e34a5..edbcbfe3 100644
--- a/cmd/writeACH/main_test.go
+++ b/cmd/writeACH/main_test.go
@@ -9,13 +9,13 @@ func TestFileWrite(t *testing.T) {
 	testFileWrite(t)
 }
 
-//BenchmarkTestFileCreate benchmarks creating an ACH File
+/*//BenchmarkTestFileCreate benchmarks creating an ACH File
 func BenchmarkTestFileWrite(b *testing.B) {
 	b.ReportAllocs()
 	for i := 0; i < b.N; i++ {
 		testFileWrite(b)
 	}
-}
+}*/
 
 // FileCreate creates an ACH File
 func testFileWrite(t testing.TB) {
diff --git a/test/ach-rck-read/main.go b/test/ach-rck-read/main.go
new file mode 100644
index 00000000..ad35f13c
--- /dev/null
+++ b/test/ach-rck-read/main.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"os"
+
+	"github.com/moov-io/ach"
+)
+
+func main() {
+	// open a file for reading. Any io.Reader Can be used
+	f, err := os.Open("rck-debit.ach")
+	if err != nil {
+		log.Panicf("Can not open file: %s: \n", 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)
+}
diff --git a/test/ach-rck-read/main_test.go b/test/ach-rck-read/main_test.go
new file mode 100644
index 00000000..45fcc779
--- /dev/null
+++ b/test/ach-rck-read/main_test.go
@@ -0,0 +1,7 @@
+package main
+
+import "testing"
+
+func Test(t *testing.T) {
+	main()
+}
diff --git a/test/ach-rck-read/rck-debit.ach b/test/ach-rck-read/rck-debit.ach
new file mode 100644
index 00000000..56df3395
--- /dev/null
+++ b/test/ach-rck-read/rck-debit.ach
@@ -0,0 +1,10 @@
+101 231380104 1210428821805310000A094101Federal Reserve Bank   My Bank Name                   
+5225Name on Account                     121042882 RCKREDEPCHECK      180601   0121042880000001
+62723138010412345678         0000002400123123123      Wade Arnold             0121042880000001
+82250000010023138010000000002400000000000000121042882                          121042880000001
+9000001000001000000010023138010000000002400000000000000                                       
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
diff --git a/test/ach-rck-write/main.go b/test/ach-rck-write/main.go
new file mode 100644
index 00000000..fa81c70f
--- /dev/null
+++ b/test/ach-rck-write/main.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+	"github.com/moov-io/ach"
+	"log"
+	"os"
+	"time"
+)
+
+func main() {
+	// Example transfer to write an ACH RCK file to debit a external institutions account
+	// Important: All financial institutions are different and will require registration and exact field values.
+
+	fh := ach.NewFileHeader()
+	fh.ImmediateDestination = "231380104" // Routing Number of the ACH Operator or receiving point to which the file is being sent
+	fh.ImmediateOrigin = "121042882"      // Routing Number of the ACH Operator or sending point that is sending the file
+	fh.FileCreationDate = time.Now()      // Today's Date
+	fh.ImmediateDestinationName = "Federal Reserve Bank"
+	fh.ImmediateOriginName = "My Bank Name"
+
+	// BatchHeader identifies the originating entity and the type of transactions contained in the batch
+	bh := ach.NewBatchHeader()
+	bh.ServiceClassCode = 225          // ACH credit pushes money out, 225 debits/pulls money in.
+	bh.CompanyName = "Name on Account" // The name of the company/person that has relationship with receiver
+	bh.CompanyIdentification = fh.ImmediateOrigin
+	bh.StandardEntryClassCode = "RCK"         // Consumer destination vs Company CCD
+	bh.CompanyEntryDescription = "REDEPCHECK" // will be on receiving accounts statement
+	bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1)
+	bh.ODFIIdentification = "121042882" // Originating Routing Number
+
+	// Identifies the receivers account information
+	// can be multiple entry's per batch
+	entry := ach.NewEntryDetail()
+	// Identifies the entry as a debit and credit entry AND to what type of account (Savings, DDA, Loan, GL)
+	entry.TransactionCode = 27          // Code 27: Debit (withdrawal) from checking account
+	entry.SetRDFI("231380104")          // Receivers bank transit routing number
+	entry.DFIAccountNumber = "12345678" // Receivers bank account number
+	entry.Amount = 2400                 // Amount of transaction with no decimal. One dollar and eleven cents = 111
+	entry.SetTraceNumber(bh.ODFIIdentification, 1)
+	entry.IndividualName = "Wade Arnold" // Identifies the receiver of the transaction
+	entry.SetCheckSerialNumber("123123123")
+
+	// build the batch
+	batch := ach.NewBatchRCK(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.AddBatch(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.WriteAll([]*ach.File{file}); err != nil {
+		log.Fatalf("Unexpected error: %s\n", err)
+	}
+	w.Flush()
+}
diff --git a/test/ach-rck-write/main_test.go b/test/ach-rck-write/main_test.go
new file mode 100644
index 00000000..45fcc779
--- /dev/null
+++ b/test/ach-rck-write/main_test.go
@@ -0,0 +1,7 @@
+package main
+
+import "testing"
+
+func Test(t *testing.T) {
+	main()
+}