Custom validation

ACH files can vary sometimes from the official NACHA guidelines due to vendor changes. Moov ACH defaults to NACHA guidelines, so to handle this there’s an exported ValidateWith(opts) method on some structures (File, FileHeader, etc).

The ValidateOpts struct can have fields added in minor releases without breaking API compatibility with callers. The default values in this struct match with NACHA’s guidelines.

Validation options

The following options can be used with File.ValidateWith and File.SetValidation to alter the default NACHA validation rules.

Note: There is a SkipAll bool validation option to bypass all validation checks.

Origin

RequireABAOrigin bool can be set to enable routing number validation over the ImmediateOrigin file header field. Often the origin can be another value which is significant to the Financial Institution you’re uploading ACH Files to.

// One-time change to the NACHA validation rules
opts := &ValidateOpts{RequireABAOrigin: true}
if err := file.Header.ValidateWith(opts); err != nil {
    // do something...
}

BypassOriginValidation bool can be set to skip all validation for the ImmediateOrigin file header field. This also allows for custom TraceNumbers which aren’t prefixed with a routing number as required by the NACHA specification.

// Override the default validation rules on an *ach.File object.
file.SetValidation(&ValidateOpts{
    BypassOriginValidation: true,
})
if err := file.Validate(); err != nil {
    // do something...
}

Destination

BypassDestinationValidation bool can be set to skip validation for the ImmediateDestination file header field.

Transaction Codes

// CheckTransactionCode allows for custom validation of TransactionCode values
CheckTransactionCode func(code int) error

Trace Numbers

The Nacha/ACH spec requires that trace numbers follow a few rules. This validation option disables them.

  • Ascending order of trace numbers within batches
  • Trace numbers beginning with their ODFI’s routing number
  • AddendaRecordIndicator is set correctly
// CustomTraceNumbers disables validation of TraceNumbers
CustomTraceNumbers bool `json:"customTraceNumbers"`

Batches

// AllowZeroBatches allows the file to have zero batches
AllowZeroBatches bool `json:"allowZeroBatches"`

// BypassCompanyIdentificationMatch allows batches in which the Company Identification field
// in the batch header and control do not match.
BypassCompanyIdentificationMatch bool `json:"bypassCompanyIdentificationMatch"`

// UnequalServiceClassCode skips equality checks for the ServiceClassCode in each pair of BatchHeader
// and BatchControl records.
UnequalServiceClassCode bool `json:"unequalServiceClassCode"`

// AllowUnorderedBatchNumebrs allows a file to be read with unordered batch numbers.
AllowUnorderedBatchNumbers bool `json:"allowUnorderedBatchNumbers"`

// UnequalAddendaCounts skips checking that Addenda Count fields match their expected and computed values.
UnequalAddendaCounts bool `json:"unequalAddendaCounts"`

Entries

// AllowInvalidAmounts will skip verifying the Amount is valid for the TransactionCode and entry type.
AllowInvalidAmounts bool `json:"allowInvalidAmounts"`

// AllowZeroEntryAmount will skip enforcing the entry Amount to be non-zero.
AllowZeroEntryAmount bool `json:"allowZeroEntryAmount"`

File Header

// AllowMissingFileHeader allows a file to be read without a FileHeader record.
AllowMissingFileHeader bool `json:"allowMissingFileHeader"`

File Control

// AllowMissingFileControl allows a file to be read without a FileControl record.
AllowMissingFileControl bool `json:"allowMissingFileControl"`

Returns

// CustomReturnCodes can be set to skip validation for the Return Code field in an Addenda99
// This allows for non-standard/deprecated return codes (e.g. R97)
CustomReturnCodes bool `json:"customReturnCodes"`

Parsing

// PreserveSpaces keeps the spacing before and after values that normally have spaces trimmed during parsing.
PreserveSpaces bool `json:"preserveSpaces"`

Reader

An ach.Reader can have custom validation rules as well, simply set them prior to reading.

r := ach.NewReader(fd) // create an ach.Reader from an os.File
r.SetValidation(&ach.ValidateOpts{
    BypassDestinationValidation: true,
})
file, err := r.Read()
if err != nil {
    // do something...
}

JSON Options

The JSON representation includes the ValidateOpts if specified on the *ach.File instance.

HTTP server

The ACH HTTP server can accept ValidateOpts when Validating a file. This will leverage the above one-off validation methods and return any errors.

Create a file

curl -X POST --data-binary @test/testdata/ppd-debit.ach http://localhost:8080/files/create
{"id":"b1910446fd904abc8b2cee358ffb3673c2cb8a62","error":null}

Apply custom validation rules

curl -X POST --data-binary '{"requireABAOrigin": true}' http://localhost:8080/files/b1910446fd904abc8b2cee358ffb3673c2cb8a62/validate
{"error":null}