Skip to content

Support separate company_name param in Nacha.build/3 #14

@edborsa

Description

@edborsa

Problem

Nacha.File.build_batch/4 hardcodes the batch header's company_name to params.immediate_origin_name:

# deps/nacha/lib/nacha/file.ex, line 120
defp build_batch({{sec, entries}, batch_num}, file, params, offset) do
  entries
  |> Batch.build(
    %{
      ...
      company_name: params.immediate_origin_name,  # <-- hardcoded
      ...
    },
    offset
  )

In the NACHA file format, these are two distinct fields at different levels:

  • File Header immediate_origin_name (23 chars) — identifies the originator to the receiving bank
  • Batch Header company_name (16 chars) — the name that appears on the end customer's bank statement

Today there is no way to set company_name independently from immediate_origin_name when using Nacha.build/3.

Real-world use case

We are integrating with Axos Bank as an ACH processor. Axos requires different company_name values depending on the transaction direction:

Field Debits Credits
immediate_origin_name (file header) RIVER. RIVER.
company_name (batch header) RIVERFININCDEBT RIVERFININC

Because the library conflates these two fields, we had to add a post-processing workaround that mutates the batch headers after Nacha.build/3 returns:

result = Nacha.build(entry_details, params)
apply_batch_company_name(result, company_name)

defp apply_batch_company_name({:ok, file}, company_name) do
  updated_batches =
    Enum.map(file.batches, fn batch ->
      %{batch | header_record: %{batch.header_record | company_name: company_name}}
    end)
  {:ok, %{file | batches: updated_batches}}
end

Proposed solution

Allow an optional company_name key in the params map passed to Nacha.build/3, falling back to immediate_origin_name when not provided. This is a one-line change in build_batch/4:

# file.ex, line 120
- company_name: params.immediate_origin_name,
+ company_name: Map.get(params, :company_name, params.immediate_origin_name),

And update the build_file_params type:

@type build_file_params :: %{
  ...
  optional(:company_name) => String.t(),
  ...
}

This is fully backwards compatible — existing callers that don't pass company_name get the same behavior as today.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions