From ac7847b01e97e29c064d81015c586b02d7af6280 Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Thu, 25 Oct 2018 22:23:34 -0400 Subject: [PATCH 01/10] Support acknowledgment entries - ACK & ATX #327 Initial ACK Support --- batch.go | 6 + batchACK.go | 89 ++++++++++++++ batchACK_test.go | 304 +++++++++++++++++++++++++++++++++++++++++++++++ batchCCD.go | 2 +- go.mod | 7 +- go.sum | 17 +++ 6 files changed, 423 insertions(+), 2 deletions(-) create mode 100644 batchACK.go create mode 100644 batchACK_test.go diff --git a/batch.go b/batch.go index 205689b55..7961e202e 100644 --- a/batch.go +++ b/batch.go @@ -27,10 +27,16 @@ 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 "ACK": + return NewBatchACK(bh), nil case "ARC": return NewBatchARC(bh), nil case "BOC": return NewBatchBOC(bh), nil + /* case "CCD", "ATX": + return NewBatchCCD(bh), nil + ToDo: Should we do it this way and slash up batchCCD? + */ case "CCD": return NewBatchCCD(bh), nil case "CIE": diff --git a/batchACK.go b/batchACK.go new file mode 100644 index 000000000..a122ab3cc --- /dev/null +++ b/batchACK.go @@ -0,0 +1,89 @@ +// Copyright 2018 The Moov 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" +) + +// BatchACK is a batch file that handles SEC payment type ACK and ACK+. +// Acknowledgement of a Corporate credit by the Receiving Depository Financial Institution (RDFI). +// For commercial accounts only. +type BatchACK struct { + batch +} + +var ( + msgBatchTransactionCodeACK = "%v transaction code not equal to 24 or 34 for SEC code %v" + msgBatchAmountACK = "%v amount must be zero for SEC code %v" +) + +// NewBatchACK returns a *BatchACK +func NewBatchACK(bh *BatchHeader) *BatchACK { + batch := new(BatchACK) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + return batch +} + +// Validate ensures the batch meets NACHA rules specific to this batch type. +func (batch *BatchACK) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + // Add configuration and type specific validation. + if batch.Header.StandardEntryClassCode != "ACK" { + msg := fmt.Sprintf(msgBatchSECType, batch.Header.StandardEntryClassCode, "ACK") + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg} + } + // Range through Entries + for _, entry := range batch.Entries { + // Amount must be zero for Acknowledgement Entries + if entry.Amount > 0 { + msg := fmt.Sprintf(msgBatchAmountACK, entry.Amount, "ACK") + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Amount", Msg: msg} + } + // TransactionCode must be either 24 or 34 for Acknowledgement Entries + switch entry.TransactionCode { + case 24, 34: + default: + msg := fmt.Sprintf(msgBatchTransactionCodeACK, entry.TransactionCode, "ACK") + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Transaction Code", Msg: msg} + } + + // ACK can have up to one Record TypeCode = 05, or there can be a NOC (98) or Return (99) + for _, addenda := range entry.Addendum { + switch entry.Category { + case CategoryForward: + if err := batch.categoryForwardAddenda05(entry, addenda); err != nil { + return err + } + if len(entry.Addendum) > 1 { + msg := fmt.Sprintf(msgBatchAddendaCount, len(entry.Addendum), 1, batch.Header.StandardEntryClassCode) + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "AddendaCount", Msg: msg} + } + case CategoryNOC: + if err := batch.categoryNOCAddenda98(entry, addenda); err != nil { + return err + } + case CategoryReturn: + if err := batch.categoryReturnAddenda99(entry, addenda); err != nil { + return err + } + } + } + } + return nil +} + +// Create builds the batch sequence numbers and batch control. Additional creation +func (batch *BatchACK) Create() error { + // generates sequence numbers and batch control + if err := batch.build(); err != nil { + return err + } + return batch.Validate() +} diff --git a/batchACK_test.go b/batchACK_test.go new file mode 100644 index 000000000..2258ba66b --- /dev/null +++ b/batchACK_test.go @@ -0,0 +1,304 @@ +// Copyright 2018 The Moov 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" +) + +// mockBatchACKHeader creates a ACK batch header +func mockBatchACKHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = 220 + bh.StandardEntryClassCode = "ACK" + bh.CompanyName = "Your Company, inc" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "Vndr Pay" + bh.ODFIIdentification = "231380104" + return bh +} + +// mockACKEntryDetail creates a ACK entry detail +func mockACKEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = 24 + entry.SetRDFI("121042882") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + // ToDo: In EntryDetail Change To Proper Name + entry.IdentificationNumber = "121042880000001" + entry.SetReceivingCompany("Best Co. #23") + entry.SetTraceNumber(mockBatchACKHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "S" + return entry +} + +// mockBatchACK creates a ACK batch +func mockBatchACK() *BatchACK { + mockBatch := NewBatchACK(mockBatchACKHeader()) + mockBatch.AddEntry(mockACKEntryDetail()) + mockBatch.GetEntries()[0].AddAddenda(mockAddenda05()) + if err := mockBatch.Create(); err != nil { + log.Fatal(err) + } + return mockBatch +} + +// testBatchACKHeader creates a ACK batch header +func testBatchACKHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchACKHeader()) + _, ok := batch.(*BatchACK) + if !ok { + t.Error("Expecting BatchACK") + } +} + +// TestBatchACKHeader tests creating a ACK batch header +func TestBatchACKHeader(t *testing.T) { + testBatchACKHeader(t) +} + +// BenchmarkBatchACKHeader benchmark creating a ACK batch header +func BenchmarkBatchACKHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKHeader(b) + } +} + +// testBatchACKAddendumCount batch control ACK can only have one addendum per entry detail +func testBatchACKAddendumCount(t testing.TB) { + mockBatch := mockBatchACK() + // Adding a second addenda to the mock entry + mockBatch.GetEntries()[0].AddAddenda(mockAddenda05()) + if err := mockBatch.Validate(); err != nil { + if e, ok := err.(*BatchError); ok { + if e.FieldName != "EntryAddendaCount" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestBatchACKAddendumCount tests batch control ACK can only have one addendum per entry detail +func TestBatchACKAddendumCount(t *testing.T) { + testBatchACKAddendumCount(t) +} + +// BenchmarkBatchACKAddendumCount benchmarks batch control ACK can only have one addendum per entry detail +func BenchmarkBatchACKAddendumCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKAddendumCount(b) + } +} + +// TestBatchACKAddendum98 validates Addenda98 returns an error +func TestBatchACKAddendum98(t *testing.T) { + mockBatch := NewBatchACK(mockBatchACKHeader()) + mockBatch.AddEntry(mockACKEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].AddAddenda(mockAddenda98) + if err := mockBatch.Create(); err != nil { + if e, ok := err.(*BatchError); ok { + if e.FieldName != "TypeCode" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestBatchACKAddendum99 validates Addenda99 returns an error +func TestBatchACKAddendum99(t *testing.T) { + mockBatch := NewBatchACK(mockBatchACKHeader()) + mockBatch.AddEntry(mockACKEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].AddAddenda(mockAddenda99) + if err := mockBatch.Create(); err != nil { + if e, ok := err.(*BatchError); ok { + if e.FieldName != "TypeCode" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} + +// testBatchACKReceivingCompanyName validates Receiving company / Individual name is a mandatory field +func testBatchACKReceivingCompanyName(t testing.TB) { + mockBatch := mockBatchACK() + // modify the Individual name / receiving company to nothing + mockBatch.GetEntries()[0].SetReceivingCompany("") + if err := mockBatch.Validate(); err != nil { + if e, ok := err.(*BatchError); ok { + if e.FieldName != "IndividualName" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestBatchACKReceivingCompanyName tests validating receiving company / Individual name is a mandatory field +func TestBatchACKReceivingCompanyName(t *testing.T) { + testBatchACKReceivingCompanyName(t) +} + +// BenchmarkBatchACKReceivingCompanyName benchmarks validating receiving company / Individual name is a mandatory field +func BenchmarkBatchACKReceivingCompanyName(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKReceivingCompanyName(b) + } +} + +// testBatchACKAddendaTypeCode validates addenda type code is 05 +func testBatchACKAddendaTypeCode(t testing.TB) { + mockBatch := mockBatchACK() + mockBatch.GetEntries()[0].Addendum[0].(*Addenda05).TypeCode = "07" + if err := mockBatch.Validate(); err != nil { + if e, ok := err.(*BatchError); ok { + if e.FieldName != "TypeCode" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestBatchACKAddendaTypeCode tests validating addenda type code is 05 +func TestBatchACKAddendaTypeCode(t *testing.T) { + testBatchACKAddendaTypeCode(t) +} + +// BenchmarkBatchACKAddendaTypeCod benchmarks validating addenda type code is 05 +func BenchmarkBatchACKAddendaTypeCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKAddendaTypeCode(b) + } +} + +// testBatchACKSEC validates that the standard entry class code is ACK for batchACK +func testBatchACKSEC(t testing.TB) { + mockBatch := mockBatchACK() + mockBatch.Header.StandardEntryClassCode = "RCK" + 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) + } + } +} + +// TestBatchACKSEC tests validating that the standard entry class code is ACK for batchACK +func TestBatchACKSEC(t *testing.T) { + testBatchACKSEC(t) +} + +// BenchmarkBatchACKSEC benchmarks validating that the standard entry class code is ACK for batch ACK +func BenchmarkBatchACKSEC(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKSEC(b) + } +} + +// testBatchACKAddendaCount validates batch ACK addenda count +func testBatchACKAddendaCount(t testing.TB) { + mockBatch := mockBatchACK() + 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) + } + } +} + +// TestBatchACKAddendaCount tests validating batch ACK addenda count +func TestBatchACKAddendaCount(t *testing.T) { + testBatchACKAddendaCount(t) +} + +// BenchmarkBatchACKAddendaCount benchmarks validating batch ACK addenda count +func BenchmarkBatchACKAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKAddendaCount(b) + } +} + +// testBatchACKCreate creates a batch ACK +func testBatchACKCreate(t testing.TB) { + mockBatch := mockBatchACK() + // Batch Header information is required to Create a batch. + mockBatch.GetHeader().ServiceClassCode = 0 + 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) + } + } +} + +// TestBatchACKCreate Test creating a batch ACK +func TestBatchACKCreate(t *testing.T) { + testBatchACKCreate(t) +} + +// BenchmarkBatchACKCreate benchmark creating a batch ACK +func BenchmarkBatchACKCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKCreate(b) + } +} + +// testBatchACKReceivingCompanyField validates ACKReceivingCompanyField +// underlying IndividualName +func testBatchACKReceivingCompanyField(t testing.TB) { + mockBatch := mockBatchACK() + ts := mockBatch.Entries[0].ReceivingCompanyField() + if ts != "Best Co. #23 " { + t.Error("Receiving Company Field is invalid") + } +} + +// TestBatchACKReceivingCompanyField tests validating ACKReceivingCompanyField +// underlying IndividualName +func TestBatchACKReceivingCompanyFieldField(t *testing.T) { + testBatchACKReceivingCompanyField(t) +} + +// BenchmarkBatchACKReceivingCompanyField benchmarks validating ACKReceivingCompanyField +// underlying IndividualName +func BenchmarkBatchACKReceivingCompanyField(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchACKReceivingCompanyField(b) + } +} diff --git a/batchCCD.go b/batchCCD.go index d0533ac7c..25b88b095 100644 --- a/batchCCD.go +++ b/batchCCD.go @@ -8,7 +8,7 @@ import ( "fmt" ) -// BatchCCD is a batch file that handles SEC payment type CCD amd CCD+. +// BatchCCD is a batch file that handles SEC payment type CCD and CCD+. // Corporate credit or debit. Identifies an Entry initiated by an Organization to transfer funds to or from an account of that Organization or another Organization. // For commercial accounts only. type BatchCCD struct { diff --git a/go.mod b/go.mod index 6ff4af0e6..6e0a1479d 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/moov-io/ach require ( github.com/VividCortex/gohistogram v1.0.0 // indirect + github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect + github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/go-kit/kit v0.7.0 github.com/go-logfmt/logfmt v0.3.0 // indirect @@ -13,8 +15,11 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/prometheus/client_golang v0.8.0 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect - github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect + github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 // indirect + github.com/sirupsen/logrus v1.1.1 // indirect golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect + golang.org/x/sys v0.0.0-20181025063200-d989b31c8746 // indirect + gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect ) diff --git a/go.sum b/go.sum index bbf8f7a1c..75f6405e2 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,12 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-kit/kit v0.7.0 h1:ApufNmWF1H6/wUbAG81hZOHmqwd0zRf8mNfLjYj/064= github.com/go-kit/kit v0.7.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= @@ -14,10 +19,13 @@ github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= +github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= @@ -26,7 +34,16 @@ github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrO github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= +github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181025063200-d989b31c8746 h1:zTiiIq2XH/ldZGPA59ILL7NbDlz/btn3iJvO7H57mY8= +golang.org/x/sys v0.0.0-20181025063200-d989b31c8746/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From 5d63140117c3f2cf6b0dc41e070b6aaedcb3a239 Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Fri, 26 Oct 2018 13:13:32 -0400 Subject: [PATCH 02/10] Support acknowledgment entries - ACK & ATX #327 create batchATX and batchATX_test CTX and ACK adjustments --- batch.go | 8 +- batchACK.go | 11 +- batchACK_test.go | 50 ++- batchATX.go | 110 +++++++ batchATX_test.go | 620 +++++++++++++++++++++++++++++++++++++ batchCTX.go | 4 +- batchCTX_test.go | 30 +- batcher.go | 3 +- entryDetail.go | 33 +- test/ach-ctx-write/main.go | 4 +- 10 files changed, 835 insertions(+), 38 deletions(-) create mode 100644 batchATX.go create mode 100644 batchATX_test.go diff --git a/batch.go b/batch.go index 7961e202e..8c22936e8 100644 --- a/batch.go +++ b/batch.go @@ -29,11 +29,13 @@ func NewBatch(bh *BatchHeader) (Batcher, error) { switch bh.StandardEntryClassCode { case "ACK": return NewBatchACK(bh), nil + case "ATX": + return NewBatchATX(bh), nil case "ARC": return NewBatchARC(bh), nil case "BOC": return NewBatchBOC(bh), nil - /* case "CCD", "ATX": + /* case "CCD", "ACK": return NewBatchCCD(bh), nil ToDo: Should we do it this way and slash up batchCCD? */ @@ -43,6 +45,10 @@ func NewBatch(bh *BatchHeader) (Batcher, error) { return NewBatchCIE(bh), nil case "COR": return NewBatchCOR(bh), nil + /* case "CTX", ATX: + return NewBatchCTX(bh), nil + ToDo: Should we do it this way and slash up batchCTX + */ case "CTX": return NewBatchCTX(bh), nil case "IAT": diff --git a/batchACK.go b/batchACK.go index a122ab3cc..7e92f698f 100644 --- a/batchACK.go +++ b/batchACK.go @@ -15,11 +15,6 @@ type BatchACK struct { batch } -var ( - msgBatchTransactionCodeACK = "%v transaction code not equal to 24 or 34 for SEC code %v" - msgBatchAmountACK = "%v amount must be zero for SEC code %v" -) - // NewBatchACK returns a *BatchACK func NewBatchACK(bh *BatchHeader) *BatchACK { batch := new(BatchACK) @@ -43,15 +38,15 @@ func (batch *BatchACK) Validate() error { for _, entry := range batch.Entries { // Amount must be zero for Acknowledgement Entries if entry.Amount > 0 { - msg := fmt.Sprintf(msgBatchAmountACK, entry.Amount, "ACK") + msg := fmt.Sprintf(msgBatchAmountZero, entry.Amount, "ACK") return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Amount", Msg: msg} } // TransactionCode must be either 24 or 34 for Acknowledgement Entries switch entry.TransactionCode { case 24, 34: default: - msg := fmt.Sprintf(msgBatchTransactionCodeACK, entry.TransactionCode, "ACK") - return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Transaction Code", Msg: msg} + msg := fmt.Sprintf(msgBatchTransactionCode, entry.TransactionCode, "ACK") + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "TransactionCode", Msg: msg} } // ACK can have up to one Record TypeCode = 05, or there can be a NOC (98) or Return (99) diff --git a/batchACK_test.go b/batchACK_test.go index 2258ba66b..a23ec65a1 100644 --- a/batchACK_test.go +++ b/batchACK_test.go @@ -248,8 +248,8 @@ func BenchmarkBatchACKAddendaCount(b *testing.B) { } } -// testBatchACKCreate creates a batch ACK -func testBatchACKCreate(t testing.TB) { +// testBatchACKServiceClassCode validates ServiceClassCode +func testBatchACKServiceClassCode(t testing.TB) { mockBatch := mockBatchACK() // Batch Header information is required to Create a batch. mockBatch.GetHeader().ServiceClassCode = 0 @@ -265,16 +265,16 @@ func testBatchACKCreate(t testing.TB) { } } -// TestBatchACKCreate Test creating a batch ACK -func TestBatchACKCreate(t *testing.T) { - testBatchACKCreate(t) +// TestBatchACKServiceClassCode tests validating ServiceClassCode +func TestBatchACKServiceClassCode(t *testing.T) { + testBatchACKServiceClassCode(t) } -// BenchmarkBatchACKCreate benchmark creating a batch ACK -func BenchmarkBatchACKCreate(b *testing.B) { +// BenchmarkBatchACKServiceClassCode benchmarks validating ServiceClassCode +func BenchmarkBatchACKServiceClassCode(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - testBatchACKCreate(b) + testBatchACKServiceClassCode(b) } } @@ -302,3 +302,37 @@ func BenchmarkBatchACKReceivingCompanyField(b *testing.B) { testBatchACKReceivingCompanyField(b) } } + +// TestBatchACKAmount validates Amount +func TestBatchACKAmount(t *testing.T) { + mockBatch := mockBatchACK() + // Batch Header information is required to Create a batch. + mockBatch.GetEntries()[0].Amount = 25000 + 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) + } + } +} + +// TestBatchACKTransactionCode validates Amount +func TestBatchACKTransactionCode(t *testing.T) { + mockBatch := mockBatchACK() + // Batch Header information is required to Create a batch. + mockBatch.GetEntries()[0].TransactionCode = 22 + mockBatch.Create() + if err := mockBatch.Validate(); 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) + } + } +} diff --git a/batchATX.go b/batchATX.go new file mode 100644 index 000000000..2f768b15f --- /dev/null +++ b/batchATX.go @@ -0,0 +1,110 @@ +// Copyright 2018 The Moov 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" +) + +// BatchATX holds the BatchHeader and BatchControl and all EntryDetail for ATX (Acknowledgment) +// Entries. +// +// The ATX entry is an acknowledgement by the Receiving Depository Financial Institution (RDFI) that a +// Corporate Credit (CTX) has been received. +type BatchATX struct { + batch +} + +var ( + msgBatchATXAddendaCount = "%v entry detail addenda records not equal to addendum %v" +) + +// NewBatchATX returns a *BatchATX +func NewBatchATX(bh *BatchHeader) *BatchATX { + batch := new(BatchATX) + batch.SetControl(NewBatchControl()) + batch.SetHeader(bh) + return batch +} + +// Validate checks valid NACHA batch rules. Assumes properly parsed records. +func (batch *BatchATX) Validate() error { + // basic verification of the batch before we validate specific rules. + if err := batch.verify(); err != nil { + return err + } + + // Add configuration and type specific validation for this type. + if batch.Header.StandardEntryClassCode != "ATX" { + msg := fmt.Sprintf(msgBatchSECType, batch.Header.StandardEntryClassCode, "ATX") + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "StandardEntryClassCode", Msg: msg} + } + + for _, entry := range batch.Entries { + // Amount must be zero for Acknowledgement Entries + if entry.Amount > 0 { + msg := fmt.Sprintf(msgBatchAmountZero, entry.Amount, "ATX") + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Amount", Msg: msg} + } + + // TransactionCode must be either 24 or 34 for Acknowledgement Entries + switch entry.TransactionCode { + // Prenote credit 23, 33, 43, 53 + // Prenote debit 28, 38, 48 + case 24, 34: + default: + msg := fmt.Sprintf(msgBatchTransactionCode, entry.TransactionCode, "ATX") + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "TransactionCode", Msg: msg} + } + + // Trapping this error, as entry.ATXAddendaRecordsField() can not be greater than 9999 + if len(entry.Addendum) > 9999 { + msg := fmt.Sprintf(msgBatchAddendaCount, len(entry.Addendum), 9999, batch.Header.StandardEntryClassCode) + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "AddendaCount", Msg: msg} + } + + // validate ATXAddendaRecord Field is equal to the actual number of Addenda records + // use 0 value if there is no Addenda records + addendaRecords, _ := strconv.Atoi(entry.CATXAddendaRecordsField()) + if len(entry.Addendum) != addendaRecords { + msg := fmt.Sprintf(msgBatchATXAddendaCount, addendaRecords, len(entry.Addendum)) + return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Addendum", Msg: msg} + } + + if len(entry.Addendum) > 0 { + + // ATX can have up to 9999 Addenda Record TypeCode = 05, or there can be a NOC (98) or Return (99) + for _, addenda := range entry.Addendum { + switch entry.Category { + case CategoryForward: + if err := batch.categoryForwardAddenda05(entry, addenda); err != nil { + return err + } + case CategoryNOC: + if err := batch.categoryNOCAddenda98(entry, addenda); err != nil { + return err + } + case CategoryReturn: + if err := batch.categoryReturnAddenda99(entry, addenda); err != nil { + return err + } + } + } + } + } + return nil +} + +// Create takes Batch Header and Entries and builds a valid batch +func (batch *BatchATX) 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/batchATX_test.go b/batchATX_test.go new file mode 100644 index 000000000..4d93700bb --- /dev/null +++ b/batchATX_test.go @@ -0,0 +1,620 @@ +// Copyright 2018 The Moov 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" +) + +// mockBatchATXHeader creates a BatchATX BatchHeader +func mockBatchATXHeader() *BatchHeader { + bh := NewBatchHeader() + bh.ServiceClassCode = 220 + bh.StandardEntryClassCode = "ATX" + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "12104288" + return bh +} + +// mockATXEntryDetail creates a BatchATX EntryDetail +func mockATXEntryDetail() *EntryDetail { + entry := NewEntryDetail() + entry.TransactionCode = 24 + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + return entry +} + +// mockBatchATX creates a BatchATX +func mockBatchATX() *BatchATX { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + mockBatch.GetEntries()[0].AddAddenda(mockAddenda05()) + if err := mockBatch.Create(); err != nil { + log.Fatal(err) + } + return mockBatch +} + +// testBatchATXHeader creates a BatchATX BatchHeader +func testBatchATXHeader(t testing.TB) { + batch, _ := NewBatch(mockBatchATXHeader()) + err, ok := batch.(*BatchATX) + if !ok { + t.Errorf("Expecting BatchATX got %T", err) + } +} + +// TestBatchATXHeader tests validating BatchATX BatchHeader +func TestBatchATXHeader(t *testing.T) { + testBatchATXHeader(t) +} + +// BenchmarkBatchATXHeader benchmarks validating BatchATX BatchHeader +func BenchmarkBatchATXHeader(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXHeader(b) + } +} + +// testBatchATXCreate validates BatchATX create +func testBatchATXCreate(t testing.TB) { + mockBatch := mockBatchATX() + if err := mockBatch.Validate(); err != nil { + t.Errorf("%T: %s", err, err) + } +} + +// TestBatchATXCreate tests validating BatchATX create +func TestBatchATXCreate(t *testing.T) { + testBatchATXCreate(t) +} + +// BenchmarkBatchATXCreate benchmarks validating BatchATX create +func BenchmarkBatchATXCreate(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXCreate(b) + } +} + +// testBatchATXStandardEntryClassCode validates BatchATX create for an invalid StandardEntryClassCode +func testBatchATXStandardEntryClassCode(t testing.TB) { + mockBatch := mockBatchATX() + 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) + } + } +} + +// TestBatchATXStandardEntryClassCode tests validating BatchATX create for an invalid StandardEntryClassCode +func TestBatchATXStandardEntryClassCode(t *testing.T) { + testBatchATXStandardEntryClassCode(t) +} + +// BenchmarkBatchATXStandardEntryClassCode benchmarks validating BatchATX create for an invalid StandardEntryClassCode +func BenchmarkBatchATXStandardEntryClassCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXStandardEntryClassCode(b) + } +} + +// testBatchATXServiceClassCodeEquality validates service class code equality +func testBatchATXServiceClassCodeEquality(t testing.TB) { + mockBatch := mockBatchATX() + 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) + } + } +} + +// TestBatchATXServiceClassCodeEquality tests validating service class code equality +func TestBatchATXServiceClassCodeEquality(t *testing.T) { + testBatchATXServiceClassCodeEquality(t) +} + +// BenchmarkBatchATXServiceClassCodeEquality benchmarks validating service class code equality +func BenchmarkBatchATXServiceClassCodeEquality(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXServiceClassCodeEquality(b) + } +} + +// testBatchATXAddendaCount validates BatchATX Addendum count of 2 +func testBatchATXAddendaCount(t testing.TB) { + mockBatch := mockBatchATX() + 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) + } + } +} + +// TestBatchATXAddendaCount tests validating BatchATX Addendum count of 2 +func TestBatchATXAddendaCount(t *testing.T) { + testBatchATXAddendaCount(t) +} + +// BenchmarkBatchATXAddendaCount benchmarks validating BatchATX Addendum count of 2 +func BenchmarkBatchATXAddendaCount(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXAddendaCount(b) + } +} + +// testBatchATXAddendaCountZero validates Addendum count of 0 +func testBatchATXAddendaCountZero(t testing.TB) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + 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) + } + } +} + +// TestBatchATXAddendaCountZero tests validating Addendum count of 0 +func TestBatchATXAddendaCountZero(t *testing.T) { + testBatchATXAddendaCountZero(t) +} + +// BenchmarkBatchATXAddendaCountZero benchmarks validating Addendum count of 0 +func BenchmarkBatchATXAddendaCountZero(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXAddendaCountZero(b) + } +} + +// testBatchATXInvalidAddendum validates Addendum must be Addenda05 +func testBatchATXInvalidAddendum(t testing.TB) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + mockBatch.GetEntries()[0].AddAddenda(mockAddenda02()) + if err := mockBatch.Create(); err != nil { + if e, ok := err.(*BatchError); ok { + if e.FieldName != "TypeCode" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestBatchATXInvalidAddendum tests validating Addendum must be Addenda05 +func TestBatchATXInvalidAddendum(t *testing.T) { + testBatchATXInvalidAddendum(t) +} + +// BenchmarkBatchATXInvalidAddendum benchmarks validating Addendum must be Addenda05 +func BenchmarkBatchATXInvalidAddendum(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXInvalidAddendum(b) + } +} + +// testBatchATXInvalidAddenda validates Addendum must be Addenda05 with record type 7 +func testBatchATXInvalidAddenda(t testing.TB) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + 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) + } + } +} + +// TestBatchATXInvalidAddenda tests validating Addendum must be Addenda05 with record type 7 +func TestBatchATXInvalidAddenda(t *testing.T) { + testBatchATXInvalidAddenda(t) +} + +// BenchmarkBatchATXInvalidAddenda benchmarks validating Addendum must be Addenda05 with record type 7 +func BenchmarkBatchATXInvalidAddenda(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXInvalidAddenda(b) + } +} + +// testBatchATXInvalidBuild validates an invalid batch build +func testBatchATXInvalidBuild(t testing.TB) { + mockBatch := mockBatchATX() + 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) + } + } +} + +// TestBatchATXInvalidBuild tests validating an invalid batch build +func TestBatchATXInvalidBuild(t *testing.T) { + testBatchATXInvalidBuild(t) +} + +// BenchmarkBatchATXInvalidBuild benchmarks validating an invalid batch build +func BenchmarkBatchATXInvalidBuild(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXInvalidBuild(b) + } +} + +// testBatchATXAddenda10000 validates error for 10000 Addenda +func testBatchATXAddenda10000(t testing.TB) { + + bh := NewBatchHeader() + bh.ServiceClassCode = 220 + bh.StandardEntryClassCode = "ATX" + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = 24 + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(9999) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchATX(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 != "AddendaCount" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestBatchATXAddenda10000 tests validating error for 10000 Addenda +func TestBatchATXAddenda10000(t *testing.T) { + testBatchATXAddenda10000(t) +} + +// BenchmarkBatchATXAddenda10000 benchmarks validating error for 10000 Addenda +func BenchmarkBatchATXAddenda10000(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXAddenda10000(b) + } +} + +// testBatchATXAddendaRecords validates error for AddendaRecords not equal to addendum +func testBatchATXAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = 220 + bh.StandardEntryClassCode = "ATX" + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = 24 + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(500) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchATX(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) + } + } +} + +// TestBatchATXAddendaRecords tests validating error for AddendaRecords not equal to addendum +func TestBatchATXAddendaRecords(t *testing.T) { + testBatchATXAddendaRecords(t) +} + +// BenchmarkBatchAddendaRecords benchmarks validating error for AddendaRecords not equal to addendum +func BenchmarkBatchATXAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXAddendaRecords(b) + } +} + +// testBatchATXReceivingCompany validates ATXReceivingCompany +func testBatchATXReceivingCompany(t testing.TB) { + mockBatch := mockBatchATX() + //mockBatch.GetEntries()[0].SetCATXReceivingCompany("Receiver") + + if mockBatch.GetEntries()[0].CATXReceivingCompanyField() != "Receiver Company" { + t.Errorf("expected %v got %v", "Receiver Company", mockBatch.GetEntries()[0].CATXReceivingCompanyField()) + } +} + +// TestBatchATXReceivingCompany tests validating ATXReceivingCompany +func TestBatchATXReceivingCompany(t *testing.T) { + testBatchATXReceivingCompany(t) +} + +// BenchmarkBatchATXReceivingCompany benchmarks validating ATXReceivingCompany +func BenchmarkBatchATXReceivingCompany(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXReceivingCompany(b) + } +} + +// testBatchATXReserved validates ATXReservedField +func testBatchATXReserved(t testing.TB) { + mockBatch := mockBatchATX() + + if mockBatch.GetEntries()[0].CATXReservedField() != " " { + t.Errorf("expected %v got %v", " ", mockBatch.GetEntries()[0].CATXReservedField()) + } +} + +// TestBatchATXReserved tests validating ATXReservedField +func TestBatchATXReserved(t *testing.T) { + testBatchATXReserved(t) +} + +// BenchmarkBatchATXReserved benchmarks validating ATXReservedField +func BenchmarkBatchATXReserved(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXReserved(b) + } +} + +// testBatchATXZeroAddendaRecords validates zero addenda records +func testBatchATXZeroAddendaRecords(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = 220 + bh.StandardEntryClassCode = "ATX" + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "12104288" + + entry := NewEntryDetail() + entry.TransactionCode = 24 + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchATX(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) + } + } +} + +// TestBatchATXZeroAddendaRecords tests validating zero addenda records +func TestBatchATXZeroAddendaRecords(t *testing.T) { + testBatchATXZeroAddendaRecords(t) +} + +// BenchmarkBatchZeroAddendaRecords benchmarks validating zero addenda records +func BenchmarkBatchATXZeroAddendaRecords(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXZeroAddendaRecords(b) + } +} + +// testBatchATXTransactionCode validates TransactionCode +func testBatchATXTransactionCode(t testing.TB) { + bh := NewBatchHeader() + bh.ServiceClassCode = 220 + bh.StandardEntryClassCode = "ATX" + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH ATX" + bh.ODFIIdentification = "12104288" + bh.OriginatorStatusCode = 2 + + entry := NewEntryDetail() + entry.TransactionCode = 23 + entry.SetRDFI("231380104") + entry.DFIAccountNumber = "744-5678-99" + entry.Amount = 0 + entry.IdentificationNumber = "45689033" + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchATX(bh) + mockBatch.AddEntry(entry) + mockBatch.GetEntries()[0].AddAddenda(mockAddenda05()) + + 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) + } + } +} + +// TestBatchATXTransactionCode tests validating prenote addenda records +func TestBatchATXTransactionCode(t *testing.T) { + testBatchATXTransactionCode(t) +} + +// BenchmarkBatchATXTransactionCode benchmarks validating prenote addenda records +func BenchmarkBatchATXTransactionCode(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + testBatchATXTransactionCode(b) + } +} + +// TestBatchATXAmount validates Amount +func TestBatchATXAmount(t *testing.T) { + bh := NewBatchHeader() + bh.ServiceClassCode = 220 + bh.StandardEntryClassCode = "ATX" + bh.CompanyName = "Payee Name" + bh.CompanyIdentification = "121042882" + bh.CompanyEntryDescription = "ACH ATX" + 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.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) + entry.DiscretionaryData = "01" + entry.Category = CategoryForward + + mockBatch := NewBatchATX(bh) + mockBatch.AddEntry(entry) + mockBatch.GetEntries()[0].AddAddenda(mockAddenda05()) + + if err := mockBatch.Create(); 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) + } + } +} + +// TestBatchATXAddendum98 validates Addenda98 returns an error +func TestBatchATXAddendum98(t *testing.T) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + mockAddenda98 := mockAddenda98() + mockAddenda98.TypeCode = "05" + mockBatch.GetEntries()[0].AddAddenda(mockAddenda98) + if err := mockBatch.Create(); err != nil { + if e, ok := err.(*BatchError); ok { + if e.FieldName != "TypeCode" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} + +// TestBatchATXAddendum99 validates Addenda99 returns an error +func TestBatchATXAddendum99(t *testing.T) { + mockBatch := NewBatchATX(mockBatchATXHeader()) + mockBatch.AddEntry(mockATXEntryDetail()) + mockAddenda99 := mockAddenda99() + mockAddenda99.TypeCode = "05" + mockBatch.GetEntries()[0].AddAddenda(mockAddenda99) + if err := mockBatch.Create(); err != nil { + if e, ok := err.(*BatchError); ok { + if e.FieldName != "TypeCode" { + t.Errorf("%T: %s", err, err) + } + } else { + t.Errorf("%T: %s", err, err) + } + } +} diff --git a/batchCTX.go b/batchCTX.go index 2c1c1b478..bd02eab84 100644 --- a/batchCTX.go +++ b/batchCTX.go @@ -54,7 +54,7 @@ func (batch *BatchCTX) Validate() error { // 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()) + addendaRecords, _ := strconv.Atoi(entry.CATXAddendaRecordsField()) if len(entry.Addendum) != addendaRecords { msg := fmt.Sprintf(msgBatchCTXAddendaCount, addendaRecords, len(entry.Addendum)) return &BatchError{BatchNumber: batch.Header.BatchNumber, FieldName: "Addendum", Msg: msg} @@ -71,7 +71,7 @@ func (batch *BatchCTX) Validate() error { default: } - // CTX can have up to one Addenda Record TypeCode = 05, or there can be a NOC (98) or Return (99) + // CTX can have up to 9999 Addenda Record TypeCode = 05, or there can be a NOC (98) or Return (99) for _, addenda := range entry.Addendum { switch entry.Category { case CategoryForward: diff --git a/batchCTX_test.go b/batchCTX_test.go index a3fc287dd..1ed5d27aa 100644 --- a/batchCTX_test.go +++ b/batchCTX_test.go @@ -29,8 +29,8 @@ func mockCTXEntryDetail() *EntryDetail { entry.DFIAccountNumber = "744-5678-99" entry.Amount = 25000 entry.IdentificationNumber = "45689033" - entry.SetCTXAddendaRecords(1) - entry.SetCTXReceivingCompany("Receiver Company") + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) entry.DiscretionaryData = "01" entry.Category = CategoryForward @@ -310,8 +310,8 @@ func testBatchCTXAddenda10000(t testing.TB) { entry.DFIAccountNumber = "744-5678-99" entry.Amount = 25000 entry.IdentificationNumber = "45689033" - entry.SetCTXAddendaRecords(9999) - entry.SetCTXReceivingCompany("Receiver Company") + entry.SetCATXAddendaRecords(9999) + entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) entry.DiscretionaryData = "01" entry.Category = CategoryForward @@ -363,8 +363,8 @@ func testBatchCTXAddendaRecords(t testing.TB) { entry.DFIAccountNumber = "744-5678-99" entry.Amount = 25000 entry.IdentificationNumber = "45689033" - entry.SetCTXAddendaRecords(500) - entry.SetCTXReceivingCompany("Receiver Company") + entry.SetCATXAddendaRecords(500) + entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) entry.DiscretionaryData = "01" entry.Category = CategoryForward @@ -403,10 +403,10 @@ func BenchmarkBatchCTXAddendaRecords(b *testing.B) { // testBatchCTXReceivingCompany validates CTXReceivingCompany func testBatchCTXReceivingCompany(t testing.TB) { mockBatch := mockBatchCTX() - //mockBatch.GetEntries()[0].SetCTXReceivingCompany("Receiver") + //mockBatch.GetEntries()[0].SetCATXReceivingCompany("Receiver") - if mockBatch.GetEntries()[0].CTXReceivingCompanyField() != "Receiver Company" { - t.Errorf("expected %v got %v", "Receiver Company", mockBatch.GetEntries()[0].CTXReceivingCompanyField()) + if mockBatch.GetEntries()[0].CATXReceivingCompanyField() != "Receiver Company" { + t.Errorf("expected %v got %v", "Receiver Company", mockBatch.GetEntries()[0].CATXReceivingCompanyField()) } } @@ -427,8 +427,8 @@ func BenchmarkBatchCTXReceivingCompany(b *testing.B) { func testBatchCTXReserved(t testing.TB) { mockBatch := mockBatchCTX() - if mockBatch.GetEntries()[0].CTXReservedField() != " " { - t.Errorf("expected %v got %v", " ", mockBatch.GetEntries()[0].CTXReservedField()) + if mockBatch.GetEntries()[0].CATXReservedField() != " " { + t.Errorf("expected %v got %v", " ", mockBatch.GetEntries()[0].CATXReservedField()) } } @@ -461,8 +461,8 @@ func testBatchCTXZeroAddendaRecords(t testing.TB) { entry.DFIAccountNumber = "744-5678-99" entry.Amount = 25000 entry.IdentificationNumber = "45689033" - entry.SetCTXAddendaRecords(1) - entry.SetCTXReceivingCompany("Receiver Company") + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) entry.DiscretionaryData = "01" entry.Category = CategoryForward @@ -511,8 +511,8 @@ func testBatchCTXPrenoteAddendaRecords(t testing.TB) { entry.DFIAccountNumber = "744-5678-99" entry.Amount = 25000 entry.IdentificationNumber = "45689033" - entry.SetCTXAddendaRecords(1) - entry.SetCTXReceivingCompany("Receiver Company") + entry.SetCATXAddendaRecords(1) + entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchCTXHeader().ODFIIdentification, 1) entry.DiscretionaryData = "01" entry.Category = CategoryForward diff --git a/batcher.go b/batcher.go index a5a9499e1..61db54209 100644 --- a/batcher.go +++ b/batcher.go @@ -64,7 +64,8 @@ var ( 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" - msgBatchTransactionCode = "Transaction code %v is not allowed for batch type %v" + msgBatchTransactionCode = "%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" + msgBatchAmountZero = "%v must be zero for SEC code %v" ) diff --git a/entryDetail.go b/entryDetail.go index 6abe83bb1..954cd893d 100644 --- a/entryDetail.go +++ b/entryDetail.go @@ -400,32 +400,63 @@ func (ed *EntryDetail) SetReceivingCompany(s string) { ed.IndividualName = s } +// ToDo: Deprecate // SetCTXAddendaRecords setter for CTX AddendaRecords characters 1-4 of underlying IndividualName func (ed *EntryDetail) SetCTXAddendaRecords(i int) { ed.IndividualName = ed.numericField(i, 4) } +// ToDo: Deprecate // SetCTXReceivingCompany setter for CTX ReceivingCompany characters 5-20 underlying IndividualName -// Position 21-22 of underlying Individual Name are reserved blank space for CTX " " +// Position 21-22 of underlying Individual Name are reserved blank space for ATX " " func (ed *EntryDetail) SetCTXReceivingCompany(s string) { ed.IndividualName = ed.IndividualName + ed.alphaField(s, 16) + " " } +// ToDo: Deprecate // 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]) } +// ToDo: Deprecate // 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]) } +// ToDo: Deprecate // CTXReservedField is used in CTX files, characters 21-22 of underlying IndividualName field func (ed *EntryDetail) CTXReservedField() string { return ed.IndividualName[20:22] } +// SetCATXAddendaRecords setter for CTX and ATX AddendaRecords characters 1-4 of underlying IndividualName +func (ed *EntryDetail) SetCATXAddendaRecords(i int) { + ed.IndividualName = ed.numericField(i, 4) +} + +// SetCATXReceivingCompany setter for CTX and ATX ReceivingCompany characters 5-20 underlying IndividualName +// Position 21-22 of underlying Individual Name are reserved blank space for CTX " " +func (ed *EntryDetail) SetCATXReceivingCompany(s string) { + ed.IndividualName = ed.IndividualName + ed.alphaField(s, 16) + " " +} + +// CATXAddendaRecordsField is used in CTX and ATX files, characters 1-4 of underlying IndividualName field +func (ed *EntryDetail) CATXAddendaRecordsField() string { + return ed.parseStringField(ed.IndividualName[0:4]) +} + +// CATXReceivingCompanyField is used in CTX and ATX files, characters 5-20 of underlying IndividualName field +func (ed *EntryDetail) CATXReceivingCompanyField() string { + return ed.parseStringField(ed.IndividualName[4:20]) +} + +// CATXReservedField is used in CTX and ATX files, characters 21-22 of underlying IndividualName field +func (ed *EntryDetail) CATXReservedField() 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) diff --git a/test/ach-ctx-write/main.go b/test/ach-ctx-write/main.go index a27f71aa9..d7f1714fb 100644 --- a/test/ach-ctx-write/main.go +++ b/test/ach-ctx-write/main.go @@ -39,8 +39,8 @@ func main() { entry.DFIAccountNumber = "12345678" // Receivers bank account number entry.Amount = 100000000 // Amount of transaction with no decimal. One dollar and eleven cents = 111 entry.IdentificationNumber = "45689033" - entry.SetCTXAddendaRecords(2) - entry.SetCTXReceivingCompany("Receiver Company") + entry.SetCATXAddendaRecords(2) + entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(bh.ODFIIdentification, 1) entry.DiscretionaryData = "01" From 3c3b14c73980367783ffed047b01f48314669060 Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Fri, 26 Oct 2018 15:32:49 -0400 Subject: [PATCH 03/10] Support acknowledgment entries - ACK & ATX #327 ACk and AXT testing adjustments --- batch.go | 12 ++---------- batchACK_test.go | 7 +++---- batchATX_test.go | 48 ++++++++++++++++++++++++------------------------ entryDetail.go | 35 ++++++++--------------------------- 4 files changed, 37 insertions(+), 65 deletions(-) diff --git a/batch.go b/batch.go index 8c22936e8..717f8acfd 100644 --- a/batch.go +++ b/batch.go @@ -29,26 +29,18 @@ func NewBatch(bh *BatchHeader) (Batcher, error) { switch bh.StandardEntryClassCode { case "ACK": return NewBatchACK(bh), nil - case "ATX": - return NewBatchATX(bh), nil case "ARC": return NewBatchARC(bh), nil + case "ATX": + return NewBatchATX(bh), nil case "BOC": return NewBatchBOC(bh), nil - /* case "CCD", "ACK": - return NewBatchCCD(bh), nil - ToDo: Should we do it this way and slash up batchCCD? - */ case "CCD": return NewBatchCCD(bh), nil case "CIE": return NewBatchCIE(bh), nil case "COR": return NewBatchCOR(bh), nil - /* case "CTX", ATX: - return NewBatchCTX(bh), nil - ToDo: Should we do it this way and slash up batchCTX - */ case "CTX": return NewBatchCTX(bh), nil case "IAT": diff --git a/batchACK_test.go b/batchACK_test.go index a23ec65a1..e5a8c318c 100644 --- a/batchACK_test.go +++ b/batchACK_test.go @@ -15,9 +15,9 @@ func mockBatchACKHeader() *BatchHeader { bh.ServiceClassCode = 220 bh.StandardEntryClassCode = "ACK" bh.CompanyName = "Your Company, inc" - bh.CompanyIdentification = "121042882" + bh.CompanyIdentification = "231380104" bh.CompanyEntryDescription = "Vndr Pay" - bh.ODFIIdentification = "231380104" + bh.ODFIIdentification = "23138010" return bh } @@ -28,8 +28,7 @@ func mockACKEntryDetail() *EntryDetail { entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - // ToDo: In EntryDetail Change To Proper Name - entry.IdentificationNumber = "121042880000001" + entry.SetOriginalTraceNumber(121042880000001) entry.SetReceivingCompany("Best Co. #23") entry.SetTraceNumber(mockBatchACKHeader().ODFIIdentification, 1) entry.DiscretionaryData = "S" diff --git a/batchATX_test.go b/batchATX_test.go index 4d93700bb..3b820256d 100644 --- a/batchATX_test.go +++ b/batchATX_test.go @@ -15,9 +15,9 @@ func mockBatchATXHeader() *BatchHeader { bh.ServiceClassCode = 220 bh.StandardEntryClassCode = "ATX" bh.CompanyName = "Payee Name" - bh.CompanyIdentification = "121042882" + bh.CompanyIdentification = "231380104" bh.CompanyEntryDescription = "ACH ATX" - bh.ODFIIdentification = "12104288" + bh.ODFIIdentification = "23138010" return bh } @@ -25,10 +25,10 @@ func mockBatchATXHeader() *BatchHeader { func mockATXEntryDetail() *EntryDetail { entry := NewEntryDetail() entry.TransactionCode = 24 - entry.SetRDFI("231380104") + entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.IdentificationNumber = "45689033" + entry.SetOriginalTraceNumber(121042880000001) entry.SetCATXAddendaRecords(1) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -300,16 +300,16 @@ func testBatchATXAddenda10000(t testing.TB) { bh.ServiceClassCode = 220 bh.StandardEntryClassCode = "ATX" bh.CompanyName = "Payee Name" - bh.CompanyIdentification = "121042882" + bh.CompanyIdentification = "231380104" bh.CompanyEntryDescription = "ACH ATX" - bh.ODFIIdentification = "12104288" + bh.ODFIIdentification = "23138010" entry := NewEntryDetail() entry.TransactionCode = 24 - entry.SetRDFI("231380104") + entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.IdentificationNumber = "45689033" + entry.SetOriginalTraceNumber(121042880000001) entry.SetCATXAddendaRecords(9999) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -353,16 +353,16 @@ func testBatchATXAddendaRecords(t testing.TB) { bh.ServiceClassCode = 220 bh.StandardEntryClassCode = "ATX" bh.CompanyName = "Payee Name" - bh.CompanyIdentification = "121042882" + bh.CompanyIdentification = "231380104" bh.CompanyEntryDescription = "ACH ATX" - bh.ODFIIdentification = "12104288" + bh.ODFIIdentification = "23138010" entry := NewEntryDetail() entry.TransactionCode = 24 - entry.SetRDFI("231380104") + entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.IdentificationNumber = "45689033" + entry.SetOriginalTraceNumber(121042880000001) entry.SetCATXAddendaRecords(500) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -451,16 +451,16 @@ func testBatchATXZeroAddendaRecords(t testing.TB) { bh.ServiceClassCode = 220 bh.StandardEntryClassCode = "ATX" bh.CompanyName = "Payee Name" - bh.CompanyIdentification = "121042882" + bh.CompanyIdentification = "231380104" bh.CompanyEntryDescription = "ACH ATX" - bh.ODFIIdentification = "12104288" + bh.ODFIIdentification = "23138010" entry := NewEntryDetail() entry.TransactionCode = 24 - entry.SetRDFI("231380104") + entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.IdentificationNumber = "45689033" + entry.SetOriginalTraceNumber(121042880000001) entry.SetCATXAddendaRecords(1) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -500,17 +500,17 @@ func testBatchATXTransactionCode(t testing.TB) { bh.ServiceClassCode = 220 bh.StandardEntryClassCode = "ATX" bh.CompanyName = "Payee Name" - bh.CompanyIdentification = "121042882" + bh.CompanyIdentification = "231380104" bh.CompanyEntryDescription = "ACH ATX" - bh.ODFIIdentification = "12104288" + bh.ODFIIdentification = "23138010" bh.OriginatorStatusCode = 2 entry := NewEntryDetail() entry.TransactionCode = 23 - entry.SetRDFI("231380104") + entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.IdentificationNumber = "45689033" + entry.SetOriginalTraceNumber(121042880000001) entry.SetCATXAddendaRecords(1) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -551,17 +551,17 @@ func TestBatchATXAmount(t *testing.T) { bh.ServiceClassCode = 220 bh.StandardEntryClassCode = "ATX" bh.CompanyName = "Payee Name" - bh.CompanyIdentification = "121042882" + bh.CompanyIdentification = "231380104" bh.CompanyEntryDescription = "ACH ATX" - bh.ODFIIdentification = "12104288" + bh.ODFIIdentification = "23138010" bh.OriginatorStatusCode = 2 entry := NewEntryDetail() entry.TransactionCode = 23 - entry.SetRDFI("231380104") + entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 25000 - entry.IdentificationNumber = "45689033" + entry.SetOriginalTraceNumber(121042880000001) entry.SetCATXAddendaRecords(1) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) diff --git a/entryDetail.go b/entryDetail.go index 954cd893d..3b10ab1eb 100644 --- a/entryDetail.go +++ b/entryDetail.go @@ -400,35 +400,16 @@ func (ed *EntryDetail) SetReceivingCompany(s string) { ed.IndividualName = s } -// ToDo: Deprecate -// SetCTXAddendaRecords setter for CTX AddendaRecords characters 1-4 of underlying IndividualName -func (ed *EntryDetail) SetCTXAddendaRecords(i int) { - ed.IndividualName = ed.numericField(i, 4) -} - -// ToDo: Deprecate -// SetCTXReceivingCompany setter for CTX ReceivingCompany characters 5-20 underlying IndividualName -// Position 21-22 of underlying Individual Name are reserved blank space for ATX " " -func (ed *EntryDetail) SetCTXReceivingCompany(s string) { - ed.IndividualName = ed.IndividualName + ed.alphaField(s, 16) + " " -} - -// ToDo: Deprecate -// 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]) -} - -// ToDo: Deprecate -// 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]) +// OriginalTraceNumberField is used in ACK and ATX files but returns the underlying IdentificationNumber field +func (ed *EntryDetail) OriginalTraceNumberField() string { + return ed.IdentificationNumberField() } -// ToDo: Deprecate -// CTXReservedField is used in CTX files, characters 21-22 of underlying IndividualName field -func (ed *EntryDetail) CTXReservedField() string { - return ed.IndividualName[20:22] +// SetOriginalTraceNumber setter for ACK and ATX OriginalTraceNumber which is underlying IdentificationNumber +func (ed *EntryDetail) SetOriginalTraceNumber(i int) { + s := strconv.Itoa(i) + //ed.IdentificationNumber = ed.numericField(i, 15) + ed.IdentificationNumber = s } // SetCATXAddendaRecords setter for CTX and ATX AddendaRecords characters 1-4 of underlying IndividualName From bb1e9fa40258a3d0001e34d2be701c5c21e1ae1b Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Fri, 26 Oct 2018 16:41:56 -0400 Subject: [PATCH 04/10] Support acknowledgment entries - ACK & ATX #327 read and write test change SetOriginalTraceNumber argument from int to string --- batchACK_test.go | 2 +- batchATX_test.go | 12 ++--- entryDetail.go | 4 +- test/ach-ack-read/ack-read.ach | 10 +++++ test/ach-ack-read/main.go | 35 +++++++++++++++ test/ach-ack-read/main_test.go | 7 +++ test/ach-ack-write/main.go | 79 +++++++++++++++++++++++++++++++++ test/ach-ack-write/main_test.go | 8 ++++ 8 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 test/ach-ack-read/ack-read.ach create mode 100644 test/ach-ack-read/main.go create mode 100644 test/ach-ack-read/main_test.go create mode 100644 test/ach-ack-write/main.go create mode 100644 test/ach-ack-write/main_test.go diff --git a/batchACK_test.go b/batchACK_test.go index e5a8c318c..14ac34d48 100644 --- a/batchACK_test.go +++ b/batchACK_test.go @@ -28,7 +28,7 @@ func mockACKEntryDetail() *EntryDetail { entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.SetOriginalTraceNumber(121042880000001) + entry.SetOriginalTraceNumber("121042880000001") entry.SetReceivingCompany("Best Co. #23") entry.SetTraceNumber(mockBatchACKHeader().ODFIIdentification, 1) entry.DiscretionaryData = "S" diff --git a/batchATX_test.go b/batchATX_test.go index 3b820256d..c5abba6b7 100644 --- a/batchATX_test.go +++ b/batchATX_test.go @@ -28,7 +28,7 @@ func mockATXEntryDetail() *EntryDetail { entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.SetOriginalTraceNumber(121042880000001) + entry.SetOriginalTraceNumber("121042880000001") entry.SetCATXAddendaRecords(1) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -309,7 +309,7 @@ func testBatchATXAddenda10000(t testing.TB) { entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.SetOriginalTraceNumber(121042880000001) + entry.SetOriginalTraceNumber("121042880000001") entry.SetCATXAddendaRecords(9999) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -362,7 +362,7 @@ func testBatchATXAddendaRecords(t testing.TB) { entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.SetOriginalTraceNumber(121042880000001) + entry.SetOriginalTraceNumber("121042880000001") entry.SetCATXAddendaRecords(500) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -460,7 +460,7 @@ func testBatchATXZeroAddendaRecords(t testing.TB) { entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.SetOriginalTraceNumber(121042880000001) + entry.SetOriginalTraceNumber("121042880000001") entry.SetCATXAddendaRecords(1) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -510,7 +510,7 @@ func testBatchATXTransactionCode(t testing.TB) { entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 0 - entry.SetOriginalTraceNumber(121042880000001) + entry.SetOriginalTraceNumber("121042880000001") entry.SetCATXAddendaRecords(1) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) @@ -561,7 +561,7 @@ func TestBatchATXAmount(t *testing.T) { entry.SetRDFI("121042882") entry.DFIAccountNumber = "744-5678-99" entry.Amount = 25000 - entry.SetOriginalTraceNumber(121042880000001) + entry.SetOriginalTraceNumber("121042880000001") entry.SetCATXAddendaRecords(1) entry.SetCATXReceivingCompany("Receiver Company") entry.SetTraceNumber(mockBatchATXHeader().ODFIIdentification, 1) diff --git a/entryDetail.go b/entryDetail.go index 3b10ab1eb..8e127def5 100644 --- a/entryDetail.go +++ b/entryDetail.go @@ -406,9 +406,7 @@ func (ed *EntryDetail) OriginalTraceNumberField() string { } // SetOriginalTraceNumber setter for ACK and ATX OriginalTraceNumber which is underlying IdentificationNumber -func (ed *EntryDetail) SetOriginalTraceNumber(i int) { - s := strconv.Itoa(i) - //ed.IdentificationNumber = ed.numericField(i, 15) +func (ed *EntryDetail) SetOriginalTraceNumber(s string) { ed.IdentificationNumber = s } diff --git a/test/ach-ack-read/ack-read.ach b/test/ach-ack-read/ack-read.ach new file mode 100644 index 000000000..f4ee7cf30 --- /dev/null +++ b/test/ach-ack-read/ack-read.ach @@ -0,0 +1,10 @@ +101 031300012 2313801041810260000A094101Federal Reserve Bank My Bank Name +5220Name on Account 231380104 ACKVndr Pay 181027 0231380100000001 +624031300012744-5678-99 0000000000031300010000001Best. #1 S 0231380100000001 +624031300012744-5678-99 0000000000031300010000002Best. #1 S 0231380100000002 +82200000020006260002000000000000000000000000231380104 231380100000001 +9000001000001000000020006260002000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 \ No newline at end of file diff --git a/test/ach-ack-read/main.go b/test/ach-ack-read/main.go new file mode 100644 index 000000000..a8e1bd0bb --- /dev/null +++ b/test/ach-ack-read/main.go @@ -0,0 +1,35 @@ +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("ack-read.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("Credit Total Amount: %v \n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("Total Amount: %v \n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("SEC Code: %v \n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Original Trace Number: %v \n", achFile.Batches[0].GetEntries()[0].OriginalTraceNumberField()) +} diff --git a/test/ach-ack-read/main_test.go b/test/ach-ack-read/main_test.go new file mode 100644 index 000000000..45fcc779c --- /dev/null +++ b/test/ach-ack-read/main_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-ack-write/main.go b/test/ach-ack-write/main.go new file mode 100644 index 000000000..3e0b7f6c5 --- /dev/null +++ b/test/ach-ack-write/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH ACK file acknowledging a credit + // 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 = "031300012" // 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 = "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 = 220 // 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 = "ACK" // Consumer destination vs Company CCD + bh.CompanyEntryDescription = "Vndr Pay" // will be on receiving accounts statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1) + bh.ODFIIdentification = "23138010" // 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 = 24 // Code 22: Demand Debit(deposit) to checking account + entry.SetRDFI("031300012") // Receivers bank transit routing number + entry.DFIAccountNumber = "744-5678-99" // Receivers bank account number + entry.Amount = 0 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetReceivingCompany("Best. #1") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.DiscretionaryData = "S" + + entryOne := ach.NewEntryDetail() // Fee Entry + entryOne.TransactionCode = 24 // Demand Credit + entryOne.SetRDFI("031300012") // Receivers bank transit routing number + entryOne.DFIAccountNumber = "744-5678-99" // Receivers bank account number + entryOne.Amount = 0 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entryOne.SetOriginalTraceNumber("031300010000002") + entryOne.SetReceivingCompany("Best. #1") + entryOne.SetTraceNumber(bh.ODFIIdentification, 2) + entryOne.DiscretionaryData = "S" + + // build the batch + batch := ach.NewBatchACK(bh) + batch.AddEntry(entry) + batch.AddEntry(entryOne) + 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.Write(file); err != nil { + log.Fatalf("Unexpected error: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-ack-write/main_test.go b/test/ach-ack-write/main_test.go new file mode 100644 index 000000000..ceb689537 --- /dev/null +++ b/test/ach-ack-write/main_test.go @@ -0,0 +1,8 @@ +package main + +import "testing" + +func Test(t *testing.T) { + main() +} + From b1d710e9e0bfac5a8e2dc24980bfba2ec5395e1e Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Fri, 26 Oct 2018 16:58:37 -0400 Subject: [PATCH 05/10] gofmt gofmt --- test/ach-ack-write/main_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/test/ach-ack-write/main_test.go b/test/ach-ack-write/main_test.go index ceb689537..45fcc779c 100644 --- a/test/ach-ack-write/main_test.go +++ b/test/ach-ack-write/main_test.go @@ -5,4 +5,3 @@ import "testing" func Test(t *testing.T) { main() } - From d2386ba39b0d79ef27f877eaf398e86810e42397 Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Mon, 29 Oct 2018 13:52:36 -0400 Subject: [PATCH 06/10] Support acknowledgment entries - ACK & ATX add ATX write and read test minor adjustments for ACK write tes --- test/ach-ack-read/ack-read.ach | 8 +-- test/ach-ack-write/main.go | 2 - test/ach-axt-read/atx-read.ach | 10 +++ test/ach-axt-read/main.go | 42 +++++++++++++ test/ach-axt-read/main_test.go | 7 +++ test/ach-axt-write/main.go | 105 ++++++++++++++++++++++++++++++++ test/ach-axt-write/main_test.go | 7 +++ 7 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 test/ach-axt-read/atx-read.ach create mode 100644 test/ach-axt-read/main.go create mode 100644 test/ach-axt-read/main_test.go create mode 100644 test/ach-axt-write/main.go create mode 100644 test/ach-axt-write/main_test.go diff --git a/test/ach-ack-read/ack-read.ach b/test/ach-ack-read/ack-read.ach index f4ee7cf30..73f1a08f9 100644 --- a/test/ach-ack-read/ack-read.ach +++ b/test/ach-ack-read/ack-read.ach @@ -1,7 +1,7 @@ -101 031300012 2313801041810260000A094101Federal Reserve Bank My Bank Name -5220Name on Account 231380104 ACKVndr Pay 181027 0231380100000001 -624031300012744-5678-99 0000000000031300010000001Best. #1 S 0231380100000001 -624031300012744-5678-99 0000000000031300010000002Best. #1 S 0231380100000002 +101 031300012 2313801041810290000A094101Federal Reserve Bank My Bank Name +5220Name on Account 231380104 ACKVndr Pay 181030 0231380100000001 +624031300012744-5678-99 0000000000031300010000001Best. #1 0231380100000001 +624031300012744-5678-99 0000000000031300010000002Best. #1 0231380100000002 82200000020006260002000000000000000000000000231380104 231380100000001 9000001000001000000020006260002000000000000000000000000 9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/ach-ack-write/main.go b/test/ach-ack-write/main.go index 3e0b7f6c5..854e004a8 100644 --- a/test/ach-ack-write/main.go +++ b/test/ach-ack-write/main.go @@ -42,7 +42,6 @@ func main() { entry.SetOriginalTraceNumber("031300010000001") entry.SetReceivingCompany("Best. #1") entry.SetTraceNumber(bh.ODFIIdentification, 1) - entry.DiscretionaryData = "S" entryOne := ach.NewEntryDetail() // Fee Entry entryOne.TransactionCode = 24 // Demand Credit @@ -52,7 +51,6 @@ func main() { entryOne.SetOriginalTraceNumber("031300010000002") entryOne.SetReceivingCompany("Best. #1") entryOne.SetTraceNumber(bh.ODFIIdentification, 2) - entryOne.DiscretionaryData = "S" // build the batch batch := ach.NewBatchACK(bh) diff --git a/test/ach-axt-read/atx-read.ach b/test/ach-axt-read/atx-read.ach new file mode 100644 index 000000000..6573c5015 --- /dev/null +++ b/test/ach-axt-read/atx-read.ach @@ -0,0 +1,10 @@ +101 031300012 2313801041810290000A094101Federal Reserve Bank My Bank Name +5220Name on Account 231380104 ATXVndr Pay 181030 0231380100000001 +624031300012744-5678-99 00000000000313000100000010002Receiver Company 011231380100000001 +705Credit account 1 for service 00010000001 +705Credit account 2 for service 00020000001 +624031300012744-5678-99 00000000000313000100000020002Receiver Company 011231380100000002 +705Credit account 1 for leadership 00010000002 +705Credit account 2 for leadership 00020000002 +82200000060006260002000000000000000000000000231380104 231380100000001 +9000001000001000000060006260002000000000000000000000000 \ No newline at end of file diff --git a/test/ach-axt-read/main.go b/test/ach-axt-read/main.go new file mode 100644 index 000000000..2f97ac9ef --- /dev/null +++ b/test/ach-axt-read/main.go @@ -0,0 +1,42 @@ +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("atx-read.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("Total Amount Credit: %v \n", achFile.Control.TotalCreditEntryDollarAmountInFile) + fmt.Printf("SEC Code: %v \n", achFile.Batches[0].GetHeader().StandardEntryClassCode) + fmt.Printf("Total Amount: %v \n", achFile.Batches[0].GetEntries()[0].Amount) + fmt.Printf("Original Trace Number: %v \n", achFile.Batches[0].GetEntries()[0].OriginalTraceNumberField()) + fmt.Printf("Addenda1: %v \n", achFile.Batches[0].GetEntries()[0].Addendum[0].String()) + fmt.Printf("Addenda2: %v \n", achFile.Batches[0].GetEntries()[0].Addendum[1].String()) + fmt.Printf("Total Amount: %v \n", achFile.Batches[0].GetEntries()[1].Amount) + fmt.Printf("Original Trace Number: %v \n", achFile.Batches[0].GetEntries()[1].OriginalTraceNumberField()) + fmt.Printf("Addenda1: %v \n", achFile.Batches[0].GetEntries()[1].Addendum[0].String()) + fmt.Printf("Addenda2: %v \n", achFile.Batches[0].GetEntries()[1].Addendum[1].String()) +} diff --git a/test/ach-axt-read/main_test.go b/test/ach-axt-read/main_test.go new file mode 100644 index 000000000..45fcc779c --- /dev/null +++ b/test/ach-axt-read/main_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func Test(t *testing.T) { + main() +} diff --git a/test/ach-axt-write/main.go b/test/ach-axt-write/main.go new file mode 100644 index 000000000..1d1500312 --- /dev/null +++ b/test/ach-axt-write/main.go @@ -0,0 +1,105 @@ +package main + +import ( + "log" + "os" + "time" + + "github.com/moov-io/ach" +) + +func main() { + // Example transfer to write an ACH AXT file acknowledging a CTX credit + // 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 = "031300012" // 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 = "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 = 220 // 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 = "ATX" // Consumer destination vs Company CCD + bh.CompanyEntryDescription = "Vndr Pay" // will be on receiving accounts statement + bh.EffectiveEntryDate = time.Now().AddDate(0, 0, 1) + bh.ODFIIdentification = "23138010" // 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 = 24 // Code 22: Demand Debit(deposit) to checking account + entry.SetRDFI("031300012") // Receivers bank transit routing number + entry.DFIAccountNumber = "744-5678-99" // Receivers bank account number + entry.Amount = 0 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entry.SetOriginalTraceNumber("031300010000001") + entry.SetCATXAddendaRecords(2) + entry.SetCATXReceivingCompany("Receiver Company") + entry.SetTraceNumber(bh.ODFIIdentification, 1) + entry.DiscretionaryData = "01" + + entryOne := ach.NewEntryDetail() // Fee Entry + entryOne.TransactionCode = 24 // Demand Credit + entryOne.SetRDFI("031300012") // Receivers bank transit routing number + entryOne.DFIAccountNumber = "744-5678-99" // Receivers bank account number + entryOne.Amount = 0 // Amount of transaction with no decimal. One dollar and eleven cents = 111 + entryOne.SetOriginalTraceNumber("031300010000002") + entryOne.SetCATXAddendaRecords(2) + entryOne.SetCATXReceivingCompany("Receiver Company") + entryOne.SetTraceNumber(bh.ODFIIdentification, 2) + entryOne.DiscretionaryData = "01" + + entryAd1 := ach.NewAddenda05() + entryAd1.PaymentRelatedInformation = "Credit account 1 for service" + entryAd1.SequenceNumber = 1 + entryAd1.EntryDetailSequenceNumber = 0000001 + + entryAd2 := ach.NewAddenda05() + entryAd2.PaymentRelatedInformation = "Credit account 2 for service" + entryAd2.SequenceNumber = 2 + entryAd2.EntryDetailSequenceNumber = 0000001 + + entryOneAd1 := ach.NewAddenda05() + entryOneAd1.PaymentRelatedInformation = "Credit account 1 for leadership" + entryOneAd1.SequenceNumber = 1 + entryOneAd1.EntryDetailSequenceNumber = 0000002 + + entryOneAd2 := ach.NewAddenda05() + entryOneAd2.PaymentRelatedInformation = "Credit account 2 for leadership" + entryOneAd2.SequenceNumber = 2 + entryOneAd2.EntryDetailSequenceNumber = 0000002 + + // build the batch + batch := ach.NewBatchATX(bh) + batch.AddEntry(entry) + batch.GetEntries()[0].AddAddenda(entryAd1) + batch.GetEntries()[0].AddAddenda(entryAd2) + batch.AddEntry(entryOne) + batch.GetEntries()[1].AddAddenda(entryOneAd1) + batch.GetEntries()[1].AddAddenda(entryOneAd2) + 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.Write(file); err != nil { + log.Fatalf("Unexpected error: %s\n", err) + } + w.Flush() +} diff --git a/test/ach-axt-write/main_test.go b/test/ach-axt-write/main_test.go new file mode 100644 index 000000000..45fcc779c --- /dev/null +++ b/test/ach-axt-write/main_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func Test(t *testing.T) { + main() +} From 0169af403d866308fc1e85fe8c087a983eaf13cb Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Mon, 29 Oct 2018 14:37:57 -0400 Subject: [PATCH 07/10] Update README.md for ACK and ATX Update README.md for ACK and ATX --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 21203e8fd..d1071d26e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ Docs: [docs.moov.io](https://docs.moov.io/en/latest/) | [api docs](https://api.m ACH is under active development but already in production for multiple companies. Please star the project if you are interested in its progress. * Library currently supports the reading and writing + * ACK (Acknowledgment Entry for CCD) * ARC (Accounts Receivable Entry) + * ATX (Acknowledgment Entry for CTX) * BOC (Back Office Conversion) * CCD (Corporate credit or debit) * CIE (Customer-Initiated Entry) From b3545e994f6f2e12b2b564281f63f6e130e6db2a Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Mon, 29 Oct 2018 16:12:05 -0400 Subject: [PATCH 08/10] remove go.mod and go.sum remove go.mod and go.sum --- go.mod | 25 ------------------------- go.sum | 49 ------------------------------------------------- 2 files changed, 74 deletions(-) delete mode 100644 go.mod delete mode 100644 go.sum diff --git a/go.mod b/go.mod deleted file mode 100644 index 6e0a1479d..000000000 --- a/go.mod +++ /dev/null @@ -1,25 +0,0 @@ -module github.com/moov-io/ach - -require ( - github.com/VividCortex/gohistogram v1.0.0 // indirect - github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect - github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect - github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect - github.com/go-kit/kit v0.7.0 - github.com/go-logfmt/logfmt v0.3.0 // indirect - github.com/go-stack/stack v1.8.0 // indirect - github.com/golang/protobuf v1.2.0 // indirect - github.com/gorilla/context v1.1.1 // indirect - github.com/gorilla/mux v1.6.2 - github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/prometheus/client_golang v0.8.0 - github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect - github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e - github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 // indirect - github.com/sirupsen/logrus v1.1.1 // indirect - golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect - golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect - golang.org/x/sys v0.0.0-20181025063200-d989b31c8746 // indirect - gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 75f6405e2..000000000 --- a/go.sum +++ /dev/null @@ -1,49 +0,0 @@ -github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-kit/kit v0.7.0 h1:ApufNmWF1H6/wUbAG81hZOHmqwd0zRf8mNfLjYj/064= -github.com/go-kit/kit v0.7.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs= -github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8= -github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= -github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= -github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= -github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181025063200-d989b31c8746 h1:zTiiIq2XH/ldZGPA59ILL7NbDlz/btn3iJvO7H57mY8= -golang.org/x/sys v0.0.0-20181025063200-d989b31c8746/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From e8940af338fcfc71e1991c660103353226041c26 Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Mon, 29 Oct 2018 16:41:30 -0400 Subject: [PATCH 09/10] replace go/mod and go.sum replace go/mod and go.sum --- go.mod | 20 ++++++++++++++++++++ go.sum | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..6ff4af0e6 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module github.com/moov-io/ach + +require ( + github.com/VividCortex/gohistogram v1.0.0 // indirect + github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect + github.com/go-kit/kit v0.7.0 + github.com/go-logfmt/logfmt v0.3.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/golang/protobuf v1.2.0 // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/mux v1.6.2 + github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/prometheus/client_golang v0.8.0 + github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect + github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect + github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 // indirect + golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect + golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..bbf8f7a1c --- /dev/null +++ b/go.sum @@ -0,0 +1,32 @@ +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/go-kit/kit v0.7.0 h1:ApufNmWF1H6/wUbAG81hZOHmqwd0zRf8mNfLjYj/064= +github.com/go-kit/kit v0.7.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 08653b1bf02a0b17559a51d6fa58420fa338969c Mon Sep 17 00:00:00 2001 From: Brooke E Kline Jr Date: Mon, 29 Oct 2018 17:19:09 -0400 Subject: [PATCH 10/10] Add back removed CTX functions Add back removed CTX functions to be deprecated in the future --- entryDetail.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/entryDetail.go b/entryDetail.go index 8e127def5..15607f687 100644 --- a/entryDetail.go +++ b/entryDetail.go @@ -410,6 +410,42 @@ func (ed *EntryDetail) SetOriginalTraceNumber(s string) { ed.IdentificationNumber = s } +// ToDo: Deprecate and use SetCATXAddendaRecords + +// SetCTXAddendaRecords setter for CTX AddendaRecords characters 1-4 of underlying IndividualName +func (ed *EntryDetail) SetCTXAddendaRecords(i int) { + ed.IndividualName = ed.numericField(i, 4) +} + +// ToDo: Deprecate and use SetCATXReceivingCompany + +// 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) + " " +} + +// ToDo: Deprecate and use CATXAddendaRecordsField + +// 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]) +} + +// ToDo: Deprecate and use CATXReceivingCompanyField + +// 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]) +} + +// ToDo: Deprecate and use CATXReservedField + +// CTXReservedField is used in CTX files, characters 21-22 of underlying IndividualName field +func (ed *EntryDetail) CTXReservedField() string { + return ed.IndividualName[20:22] +} + // SetCATXAddendaRecords setter for CTX and ATX AddendaRecords characters 1-4 of underlying IndividualName func (ed *EntryDetail) SetCATXAddendaRecords(i int) { ed.IndividualName = ed.numericField(i, 4)