Workers compensation payments. Byte4Bit. Architecture, build and deploy of the ACH processor.
Audit1ACH
I designed, built and deployed the ACH payment processor that collects workers compensation premiums by generating and reconciling NACHA files.
The problem
Collecting insurance premiums by ACH means producing NACHA files a bank will accept, and then living with everything that comes back days later: returns, rejects, status changes and the reconciliation nobody sees until it is wrong. A file that is malformed is not a validation error, it is money that did not move.
I owned the architecture, the implementation and the deployment of the processor. The domain layer knows nothing about HTTP, Mongo or the message bus: NACHA generation, returns handling and reconciliation are pure logic behind ports, with adapters for everything that changes.
The file format is domain logic, not glue
NACHA generation and parsing live in the domain, testable without a database or a bank. The adapters that move the files can be swapped without touching the rules that make them valid.
Returns are part of the design, not an afterthought
Returns, transaction status and cutover each got their own service, because the interesting part of ACH is not the send, it is the days that follow it.
Deployed as code
The processor ships with its own Terraform and container build, so an environment is a reviewable artifact rather than a runbook someone remembers.
Decisions and the price I paid for them
Hexagonal architecture with the domain at the centre
The core processes payments, so the structure should say that. Dependencies point inward, and infrastructure depends on the business logic rather than the other way around.
Trade-off accepted
More indirection than a straight service layer, and a team has to understand ports and adapters before it can move quickly in the codebase.
Validate before anything reaches the bank
A rejected NACHA file costs a settlement cycle, so schema validation sits in front of every write and every generated file rather than at the edge only.
Trade-off accepted
Stricter rejection at the boundary, and more work to let a legitimate but unusual case through.
Asynchronous by message, not by polling
Ingestion, processing, returns and reconciliation are separate concerns that fail separately, so they talk over a message bus with retries instead of one long synchronous chain.
Trade-off accepted
Eventual consistency the product has to express, and idempotency that every consumer has to earn.