// Licensed to The Moov Authors under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. The Moov Authors licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package ach import ( "fmt" "strconv" "strings" "unicode/utf8" ) // BatchENR is a non-monetary entry that enrolls a person with an agency of the US government // for a depository financial institution. // // Allowed TransactionCode values: 22 Demand Credit, 27 Demand Debit, 32 Savings Credit, 37 Savings Debit type BatchENR struct { Batch } // NewBatchENR returns a *BatchENR func NewBatchENR(bh *BatchHeader) *BatchENR { batch := new(BatchENR) batch.SetControl(NewBatchControl()) batch.SetHeader(bh) batch.SetID(bh.ID) return batch } // Validate ensures the batch meets NACHA rules specific to this batch type. func (batch *BatchENR) Validate() error { if err := batch.verify(); err != nil { return err } // Batch Header checks if batch.Header.StandardEntryClassCode != ENR { return batch.Error("StandardEntryClassCode", ErrBatchSECType, ENR) } if batch.Header.CompanyEntryDescription != "AUTOENROLL" { return batch.Error("CompanyEntryDescription", ErrBatchCompanyEntryDescriptionAutoenroll, batch.Header.CompanyEntryDescription) } // Range over Entries for _, entry := range batch.Entries { if err := entry.Validate(); err != nil { return err } if entry.Amount != 0 { return batch.Error("Amount", ErrBatchAmountNonZero, entry.Amount) } switch entry.TransactionCode { case CheckingPrenoteCredit, SavingsPrenoteCredit: // nothing default: return batch.Error("TransactionCode", ErrBatchTransactionCode, entry.TransactionCode) } // Verify the Amount is valid for SEC code and TransactionCode if err := batch.ValidAmountForCodes(entry); err != nil { return err } // Verify the TransactionCode is valid for a ServiceClassCode if err := batch.ValidTranCodeForServiceClassCode(entry); err != nil { return err } // ENR must have one Addenda05 // Verify Addenda* FieldInclusion based on entry.Category and batchHeader.StandardEntryClassCode if err := batch.addendaFieldInclusion(entry); err != nil { return err } } return nil } // Create will tabulate and assemble an ACH batch into a valid state. This includes // setting any posting dates, sequence numbers, counts, and sums. // // Create implementations are free to modify computable fields in a file and should // call the Batch's Validate function at the end of their execution. func (batch *BatchENR) Create() error { // generates sequence numbers and batch control if err := batch.build(); err != nil { return err } return batch.Validate() } // ENRPaymentInformation structure type ENRPaymentInformation struct { // TransactionCode is the Transaction Code of the holder's account // Values: 22 (Demand Credit), 27 (Demand Debit), 32 (Savings Credit), 37 (Savings Debit) TransactionCode int // RDFIIdentification is the Receiving Depository Identification Number. Typically the first 8 of their ABA routing number. RDFIIdentification string // CheckDigit is the last digit from an ABA routing number. CheckDigit string // DFIAccountNumber contains the holder's account number. DFIAccountNumber string // IndividualIdentification contains the customer's Social Security Number (SSN) for automated enrollments and the // taxpayer ID for companies. IndividualIdentification string // IndividualName is the account holders full name. IndividualName string // EnrolleeClassificationCode (also called Representative Payee Indicator) returns a code from a specific Addenda05 record. // These codes represent: // 0: (no) - Initiated by beneficiary // 1: (yes) - Initiated by someone other than named beneficiary // A: Enrollee is a consumer // b: Enrollee is a company EnrolleeClassificationCode string } func (info ENRPaymentInformation) String() string { // Stretch the companies name across two fields var individualName string if strings.EqualFold(info.EnrolleeClassificationCode, "B") { // First fifteen characters are added individualName = strings.TrimSpace(fmt.Sprintf("%15.15s", info.IndividualName)) + "*" // Add on a second field if needed runes := utf8.RuneCountInString(info.IndividualName) if runes > 15 { individualName += strings.TrimSpace(fmt.Sprintf("%7.7s", info.IndividualName[15:])) } } else { // Format the Individual's name by Surname first nameParts := strings.Fields(info.IndividualName) if len(nameParts) > 1 { // Surname comes fist nameParts = append(nameParts[len(nameParts)-1:], nameParts[:len(nameParts)-1]...) } individualName = strings.Join(nameParts, "*") } return fmt.Sprintf(`%v*%v*%v*%v*%v*%v*%v\`, info.TransactionCode, info.RDFIIdentification, info.CheckDigit, info.DFIAccountNumber, info.IndividualIdentification, individualName, info.EnrolleeClassificationCode) } // ParseENRPaymentInformation returns an ENRPaymentInformation for a given Addenda05 record. The information is parsed from the addenda's // PaymentRelatedInformation field. // // The returned information is not validated for correctness. func ParseENRPaymentInformation(addenda05 *Addenda05) (*ENRPaymentInformation, error) { if addenda05 == nil { return nil, nil } parts := strings.Split(strings.TrimSuffix(addenda05.PaymentRelatedInformation, `\`), "*") // PaymentRelatedInformation is terminated by '\' if len(parts) != 8 { return nil, fmt.Errorf("ENR: unable to parse Addenda05 (%s) PaymentRelatedInformation", addenda05.ID) } txCode, err := strconv.Atoi(parts[0]) if err != nil { return nil, fmt.Errorf("ENR: unable to parse TransactionCode (%s) from Addenda05.ID=%s", parts[0], addenda05.ID) } enrolleeClassificationCode := parts[7] individualName := fmt.Sprintf("%s %s", parts[6], parts[5]) if strings.EqualFold(enrolleeClassificationCode, "B") { // Business Names can be fill two field lengths individualName = fmt.Sprintf("%s%s", parts[5], parts[6]) } return &ENRPaymentInformation{ TransactionCode: txCode, RDFIIdentification: parts[1], CheckDigit: parts[2], DFIAccountNumber: parts[3], IndividualIdentification: parts[4], IndividualName: individualName, EnrolleeClassificationCode: enrolleeClassificationCode, }, nil }