├── .github ├── CODEOWNERS └── workflows │ ├── codeql.yaml │ ├── go.yml │ ├── podman.yml │ └── release.yml ├── .gitignore ├── ACHDictionary.go ├── ACHDictionary_test.go ├── AUTHORS ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Dockerfile-fedtest ├── Dockerfile-openshift ├── LICENSE ├── README.md ├── WIREDictionary.go ├── WIREDictionary_test.go ├── api └── client.yaml ├── client ├── .gitignore ├── .openapi-generator-ignore ├── .openapi-generator │ └── VERSION ├── README.md ├── api │ └── openapi.yaml ├── api_fed.go ├── client.go ├── configuration.go ├── docs │ ├── AchDictionary.md │ ├── AchLocation.md │ ├── AchParticipant.md │ ├── Error.md │ ├── FEDApi.md │ ├── Logo.md │ ├── WireDictionary.md │ ├── WireLocation.md │ └── WireParticipant.md ├── model_ach_dictionary.go ├── model_ach_location.go ├── model_ach_participant.go ├── model_error.go ├── model_logo.go ├── model_wire_dictionary.go ├── model_wire_location.go ├── model_wire_participant.go └── response.go ├── cmd ├── fedtest │ ├── ach.go │ ├── main.go │ └── wire.go └── server │ ├── http.go │ ├── main.go │ ├── main_test.go │ ├── reader.go │ ├── reader_test.go │ ├── search.go │ ├── search_handlers.go │ ├── search_handlers_test.go │ └── search_test.go ├── const.go ├── data ├── FedACHdir.txt ├── fedachdir.json ├── fpddir.json └── fpddir.txt ├── docker-compose.yml ├── docs ├── 404.html ├── FEDACHDIR_FORMAT.MD ├── FedACHdir.md ├── Fed_STATE_CODES.md ├── Gemfile ├── Gemfile.lock ├── README.md ├── _config.yml ├── _data │ ├── docs-menu.yml │ └── navigation.yml ├── api │ └── index.html ├── favicon.png ├── file-structure.md ├── fpddir.md ├── fpddir_FORMAT.md ├── index.md ├── intro.md ├── kubernetes.md ├── prometheus.md ├── usage-binary.md ├── usage-configuration.md ├── usage-docker.md ├── usage-go.md └── usage-google-cloud.md ├── fileErrors.go ├── go.mod ├── go.sum ├── makefile ├── normalize.go ├── normalize_test.go ├── openapi-generator ├── openapi.yaml ├── pkg ├── download │ ├── download.go │ └── download_test.go └── strcmp │ ├── strcmp.go │ └── strcmp_test.go ├── renovate.json ├── validators.go └── version.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @adamdecaf 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yaml: -------------------------------------------------------------------------------- 1 | name: CodeQL Analysis 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * 0' 8 | 9 | jobs: 10 | CodeQL-Build: 11 | strategy: 12 | fail-fast: false 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Initialize CodeQL 19 | uses: github/codeql-action/init@v3 20 | with: 21 | languages: go 22 | 23 | - name: Perform CodeQL Analysis 24 | uses: github/codeql-action/analyze@v3 25 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: Go Build 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | steps: 17 | - name: Set up Go 1.x 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: stable 21 | id: go 22 | 23 | - name: Check out code into the Go module directory 24 | uses: actions/checkout@v4 25 | 26 | - name: Install make (Windows) 27 | if: runner.os == 'Windows' 28 | run: choco install -y make mingw 29 | 30 | - name: Build 31 | run: make build 32 | 33 | - name: Check 34 | run: make check 35 | 36 | docker: 37 | name: Docker Build 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Set up Go 1.x 41 | uses: actions/setup-go@v5 42 | with: 43 | go-version: 'stable' 44 | id: go 45 | 46 | - name: Check out code into the Go module directory 47 | uses: actions/checkout@v4 48 | with: 49 | fetch-depth: 0 50 | 51 | - name: Docker Build 52 | if: runner.os == 'Linux' 53 | run: make docker && make build && make test-integration && make clean-integration 54 | -------------------------------------------------------------------------------- /.github/workflows/podman.yml: -------------------------------------------------------------------------------- 1 | name: Podman 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: Go Build 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | steps: 17 | - name: Set up Go 1.x 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: stable 21 | id: go 22 | 23 | - name: Clone the repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Build 27 | run: make build 28 | 29 | - name: Check 30 | run: make check 31 | 32 | - name: Buildah Action 33 | id: build-image 34 | if: ${{ github.event.pull_request.head.repo.full_name == 'moov-io/fed' }} 35 | uses: redhat-actions/buildah-build@v2 36 | with: 37 | image: moov/fed 38 | tags: podman-${{ github.sha }} 39 | containerfiles: | 40 | ./Dockerfile 41 | 42 | - name: Log in to the GitHub Container registry 43 | if: ${{ github.event.pull_request.head.repo.full_name == 'moov-io/fed' }} 44 | uses: redhat-actions/podman-login@v1 45 | with: 46 | registry: docker.io 47 | username: ${{ secrets.DOCKER_USERNAME }} 48 | password: ${{ secrets.DOCKER_PASSWORD }} 49 | 50 | - name: Push to GitHub Container Repository 51 | if: ${{ github.event.pull_request.head.repo.full_name == 'moov-io/fed' }} 52 | id: push-to-ghcr 53 | uses: redhat-actions/push-to-registry@v2 54 | with: 55 | image: ${{ steps.build-image.outputs.image }} 56 | tags: ${{ steps.build-image.outputs.tags }} 57 | registry: docker.io 58 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: [ "v*.*.*" ] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | testing: 12 | name: Testing 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, macos-latest, windows-latest] 17 | steps: 18 | - name: Set up Go 1.x 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: stable 22 | id: go 23 | 24 | - name: Check out code 25 | uses: actions/checkout@v4 26 | 27 | - name: Check 28 | run: make check 29 | 30 | create_release: 31 | name: Create Release 32 | needs: [testing] 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Create Release 36 | id: create_release 37 | uses: actions/create-release@v1 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | tag_name: ${{ github.ref }} 42 | release_name: Release ${{ github.ref }} 43 | prerelease: true 44 | 45 | - name: Output Release URL File 46 | run: echo "${{ steps.create_release.outputs.upload_url }}" > release_url.txt 47 | 48 | - name: Save Release URL File for publish 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: release_url 52 | path: release_url.txt 53 | retention-days: 1 54 | 55 | publish: 56 | name: Publish 57 | needs: [testing, create_release] 58 | runs-on: ${{ matrix.os }} 59 | strategy: 60 | matrix: 61 | os: [ubuntu-latest, macos-latest, windows-latest] 62 | steps: 63 | - name: Set up Go 1.x 64 | uses: actions/setup-go@v5 65 | with: 66 | go-version: stable 67 | id: go 68 | 69 | - name: Check out code 70 | uses: actions/checkout@v4 71 | 72 | - name: Load Release URL File from release job 73 | uses: actions/download-artifact@v4 74 | with: 75 | name: release_url 76 | path: release_url 77 | 78 | - name: Distribute 79 | run: make dist 80 | 81 | - name: Get Release File Name & Upload URL 82 | id: get_release_info 83 | shell: bash 84 | run: | 85 | value=`cat release_url/release_url.txt` 86 | echo ::set-output name=upload_url::$value 87 | env: 88 | TAG_REF_NAME: ${{ github.ref }} 89 | REPOSITORY_NAME: ${{ github.repository }} 90 | 91 | - name: Upload Linux Server Binary 92 | if: runner.os == 'Linux' 93 | uses: actions/upload-release-asset@v1 94 | env: 95 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 96 | with: 97 | upload_url: ${{ steps.get_release_info.outputs.upload_url }} 98 | asset_path: ./bin/fed-linux-amd64 99 | asset_name: fed-linux-amd64 100 | asset_content_type: application/octet-stream 101 | 102 | - name: Upload macOS Server Binary 103 | if: runner.os == 'macOS' 104 | uses: actions/upload-release-asset@v1 105 | env: 106 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 107 | with: 108 | upload_url: ${{ steps.get_release_info.outputs.upload_url }} 109 | asset_path: ./bin/fed-darwin-amd64 110 | asset_name: fed-darwin-amd64 111 | asset_content_type: application/octet-stream 112 | 113 | - name: Upload Windows Server Binary 114 | if: runner.os == 'Windows' 115 | uses: actions/upload-release-asset@v1 116 | env: 117 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 118 | with: 119 | upload_url: ${{ steps.get_release_info.outputs.upload_url }} 120 | asset_path: ./bin/fed.exe 121 | asset_name: fed.exe 122 | asset_content_type: application/octet-stream 123 | 124 | docker: 125 | name: Docker 126 | needs: [testing, create_release] 127 | runs-on: ubuntu-latest 128 | steps: 129 | - name: Set up Go 1.x 130 | uses: actions/setup-go@v5 131 | with: 132 | go-version: stable 133 | id: go 134 | 135 | - name: Check out code 136 | uses: actions/checkout@v4 137 | 138 | - name: Docker 139 | run: make docker 140 | 141 | - name: Docker Push 142 | run: |+ 143 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin 144 | make release-push 145 | env: 146 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 147 | DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} 148 | 149 | - name: Quay.io Push 150 | run: |+ 151 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin quay.io 152 | make quay-push 153 | env: 154 | DOCKER_USERNAME: ${{ secrets.QUAY_USERNAME }} 155 | DOCKER_PASSWORD: ${{ secrets.QUAY_PASSWORD }} 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | .DS_Store 4 | 5 | #####=== Go ===##### 6 | 7 | /bin/ 8 | openapi-generator*jar 9 | 10 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 11 | *.o 12 | *.a 13 | *.so 14 | 15 | # Folders 16 | _obj 17 | _test 18 | 19 | # Architecture specific extensions/prefixes 20 | *.[568vq] 21 | [568vq].out 22 | 23 | *.cgo1.go 24 | *.cgo2.c 25 | _cgo_defun.c 26 | _cgo_gotypes.go 27 | _cgo_export.* 28 | 29 | _testmain.go 30 | 31 | *.exe 32 | *.test 33 | *.prof 34 | 35 | .vscode/launch.json 36 | 37 | # code coverage 38 | coverage.html 39 | cover.out 40 | coverage.txt 41 | misspell* 42 | /lint-project.sh 43 | gitleaks.tar.gz 44 | .idea/ 45 | .vscode/ 46 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This file lists all individuals having contributed content to the repository. 2 | # For how it is generated, see `make AUTHORS`. 3 | 4 | Adam Shannon 5 | Andrew Heavin 6 | Brooke Kline 7 | Kalamity 8 | Nathan Lakritz 9 | Ray Johnson 10 | rayjlinden <42587610+rayjlinden@users.noreply.github.com> 11 | Wade Arnold 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.13.0 (Released 2025-05-15) 2 | 3 | ADDITIONS 4 | 5 | - feat: read files from INITIAL_DATA_DIRECTORY 6 | 7 | IMPROVEMENTS 8 | 9 | - cmd/server: fail on startup when zero participants are found 10 | 11 | BUILD 12 | 13 | - chore(deps): update dependency go to v1.24.3 (#338) 14 | - fix(deps): update module github.com/moov-io/base to v0.55.0 (#339) 15 | - fix(deps): update module golang.org/x/oauth2 to v0.30.0 (#336) 16 | - fix(deps): update module golang.org/x/text to v0.25.0 (#337) 17 | 18 | ## v0.12.1 (Released 2025-04-22) 19 | 20 | IMPROVEMENTS 21 | 22 | - cmd/server: improve startup logging 23 | - cmd/server: return list stats in search response 24 | 25 | BUILD 26 | 27 | - build: update dependencies 28 | - chore(deps): update dependency go to v1.24.1 (#319) 29 | 30 | ## v0.11.1 (Released 2024-03-05) 31 | 32 | IMPROVEMENTS 33 | 34 | - fix: wire file parse bug, improve logic for picking json vs plaintext parser 35 | 36 | ## v0.11.0 (Released 2024-02-27) 37 | 38 | IMPROVEMENTS 39 | 40 | - feat: Enable fedach and fedwire download from proxy 41 | - fix: close xml encoder and remove unneeded panics 42 | 43 | BUILD 44 | 45 | - build: use latest stable Go release 46 | - fix(deps): update module golang.org/x/oauth2 to v0.16.0 47 | - fix(deps): update module github.com/moov-io/base to v0.48.5 48 | 49 | ## v0.10.2 (Released 2023-06-15) 50 | 51 | IMPROVEMENTS 52 | 53 | - Use Go 1.20.x in build, update deps 54 | 55 | ## v0.10.1 (Released 2023-04-19) 56 | 57 | REVERTS 58 | 59 | - client: revert openapi-generator back to 4.3.1 60 | 61 | ## v0.10.0 (Released 2023-04-12) 62 | 63 | IMPROVEMENTS 64 | 65 | - chore: update openapi-generator from 4.2.2 to 6.5.0 66 | - fix: lowercase ach/wire participants in OpenAPI 67 | 68 | BUILD 69 | 70 | - build(deps): bump nokogiri from 1.13.10 to 1.14.3 in /docs 71 | - build(deps): bump github.com/moov-io/base from v0.39.0 to v0.40.1 72 | 73 | ## v0.9.2 (Released 2023-04-07) 74 | 75 | IMPROVEMENTS 76 | 77 | - fix: check typecast of Logo 78 | 79 | BUILD 80 | 81 | - build: upgrade golang to 1.20 82 | - fix(deps): update module github.com/moov-io/base to v0.39.0 83 | - bump golang.org/x/net from 0.6.0 to 0.7.0 84 | - build: update github.com/stretchr/testify to v1.8.2 85 | 86 | ## v0.9.1 (Released 2022-09-29) 87 | 88 | BUILD 89 | 90 | - build: remove deprecated ioutil functions 91 | - fix(deps): update golang.org/x/oauth2 digest to f213421 92 | - fix(deps): update module github.com/moov-io/base to v0.35.0 93 | 94 | ## v0.9.0 (Released 2022-08-03) 95 | 96 | IMPROVEMENTS 97 | 98 | - Remove `DOWNLOAD_DIRECTORY` and store downloaded files in memory. 99 | 100 | ## v0.8.1 (Released 2022-08-02) 101 | 102 | IMPROVEMENTS 103 | 104 | - fix: remove achParticipants or wireParticipants from json responses 105 | 106 | BUILD 107 | 108 | - build: require Go 1.18 and set ReadHeaderTimeout 109 | - fix(deps): update module github.com/moov-io/base to v0.33.0 110 | - fix(deps): update golang.org/x/oauth2 digest to 128564f 111 | 112 | ## v0.8.0 (Released 2022-05-25) 113 | 114 | ADDITIONS 115 | 116 | - feat: add clearbit logos in responses when configured 117 | - feat: normalize FRB names prior to clearbit search 118 | 119 | IMPROVEMENTS 120 | 121 | - fix: improve name search by using cleaned name 122 | - refactor: cleanup duplicate code in search logic 123 | 124 | BUILD 125 | 126 | - build: update codeql action 127 | - build(deps): bump nokogiri from 1.13.4 to 1.13.6 in /docs 128 | 129 | ## v0.7.4 (Released 2022-05-18) 130 | 131 | BUILD 132 | 133 | - build: update base images 134 | - build(deps): bump nokogiri from 1.13.3 to 1.13.4 in /docs 135 | - fix(deps): update golang.org/x/oauth2 digest to 9780585 136 | 137 | ## v0.7.3 (Released 2022-04-04) 138 | 139 | IMPROVEMENTS 140 | 141 | - fix: replace deprecated strings.Title 142 | 143 | ## v0.7.2 (Released 2022-04-04) 144 | 145 | BUILD 146 | 147 | - build(deps): bump nokogiri from 1.12.5 to 1.13.3 in /docs 148 | - fix(deps): update golang.org/x/oauth2 commit hash to ee48083 149 | - fix(deps): update module github.com/go-kit/kit to v0.12.0 150 | - fix(deps): update module github.com/moov-io/base to v0.28.1 151 | - fix(deps): update module github.com/prometheus/client_golang to v1.12.1 152 | 153 | ## v0.7.1 (Released 2021-07-16) 154 | 155 | BUILD 156 | 157 | - build(deps): bump addressable from 2.7.0 to 2.8.0 in /docs 158 | - build(deps): bump nokogiri from 1.11.1 to 1.11.5 in /docs 159 | - fix(deps): update golang.org/x/oauth2 commit hash to d040287 160 | - fix(deps): update module github.com/go-kit/kit to v0.11.0 161 | 162 | ## v0.7.0 (Released 2021-05-19) 163 | 164 | ADDITIONS 165 | 166 | - Read `DOWNLOAD_DIRECTORY` environment variable for storing downloaded files. 167 | 168 | IMPROVEMENTS 169 | 170 | - search: rank results based on fuzzy score rather than name, offer exect routing number matching 171 | 172 | BUG FIXES 173 | 174 | - Fix file download errors in Docker images 175 | - De-duplicate search results, improve performance 176 | 177 | BUILD 178 | 179 | - build(deps): bump rexml from 3.2.4 to 3.2.5 in /docs 180 | 181 | ## v0.6.0 (Released 2021-04-14) 182 | 183 | ADDITIONS 184 | 185 | - cmd/server: download files if env vars are populated (`FRB_ROUTING_NUMBER` and `FRB_DOWNLOAD_CODE`) 186 | 187 | BUILD 188 | 189 | - fix(deps): update module github.com/clearbit/clearbit-go to v1.0.1 190 | 191 | ## v0.5.3 (Released 2021-02-23) 192 | 193 | IMPROVEMENTS 194 | 195 | - chore(deps): update golang docker tag to v1.16 196 | 197 | ## v0.5.2 (Released 2021-01-22) 198 | 199 | IMPROVEMENTS 200 | 201 | - chore(deps): update github.com/xrash/smetrics commit hash to 89a2a8a 202 | 203 | BUG FIXES 204 | 205 | - build: fixup for OpenShift image running 206 | 207 | BUILD 208 | 209 | - chore(deps): update golang docker tag to v1.15 210 | - chore(deps): update module gorilla/mux to v1.8.0 211 | 212 | ## v0.5.1 (Released 2020-07-07) 213 | 214 | BUULD 215 | 216 | - build: add OpenShift [`quay.io/moov/fed`](https://quay.io/repository/moov/fed) Docker image 217 | - build: convert to Actions from TravisCI 218 | - chore(deps): update module prometheus/client_golang to v1.7.0 219 | - chore(deps): upgrade github.com/gorilla/websocket to v1.4.2 220 | 221 | ## v0.5.0 (Released 2020-04-14) 222 | 223 | ADDITIONS 224 | 225 | - ach: support reading input files in the official JSON format 226 | - wire: read official JSON data files 227 | 228 | BUILD 229 | 230 | - wire: read official JSON data files 231 | 232 | ## v0.4.3 (Released 2020-03-16) 233 | 234 | BUILD 235 | 236 | - Fix `make dist` on Windows 237 | 238 | ## v0.4.2 (Released 2020-03-16) 239 | 240 | ADDITIONS 241 | 242 | - build: release windows binary 243 | 244 | IMPROVEMENTS 245 | 246 | - api: use shared Error model 247 | - docs: clarify included data files are old 248 | 249 | BUILD 250 | 251 | - chore(deps): update golang docker tag to v1.14 252 | - Update module prometheus/client_golang to v1.3.0 253 | - chore(deps): update golang.org/x/oauth2 commit hash to bf48bf1 254 | - build: run sonatype-nexus-community/nancy in CI 255 | 256 | ## v0.4.1 (Released 2019-12-17) 257 | 258 | IMPROVEMENTS 259 | 260 | - build: slim down final image, run as moov user 261 | 262 | BUILD 263 | 264 | - build: test docker image in CI 265 | - Update module prometheus/client_golang to v1.2.1 266 | - build: upgrade openapi-generator to 4.2.2 267 | 268 | ## v0.4.0 (Released 2019-10-07) 269 | 270 | BUG FIXES 271 | 272 | - changing ach participant model so AchLocation isn't a list (#68) 273 | - cmd/server: return after marshaling errNoSearchParams 274 | 275 | IMPROVEMENTS 276 | 277 | - cmd/fedtest: initial binary to perform ACH and Wire searches 278 | - cmd/server: log x-request-id and x-user-id HTTP headers 279 | 280 | BUILD 281 | 282 | - update module moov-io/base to v0.10.0 283 | - build: upgrade to Go 1.13 and Debian 10 284 | 285 | ## v0.3.0 (Released 2019-08-16) 286 | 287 | BREAKING CHANGES 288 | 289 | We've renamed all OpenAPI fields like `Id` to `ID` to be consistent with Go's style. 290 | 291 | ADDITIONS 292 | 293 | - add environment variables to override command line flags (`LOG_FORMAT`, `HTTP_BIND_ADDRESS`, `HTTP_ADMIN_BIND_ADDRESS`) 294 | - cmd/server: bind HTTP server with TLS if HTTPS_* variables are defined 295 | 296 | IMPROVEMENTS 297 | 298 | - docs: update docs.moov.io links after design refresh 299 | - docs: link to app specific docs.moov.io page 300 | - cmd/server: quit with an exit code of 1 on missing data files 301 | 302 | BUILD 303 | 304 | - chore(deps): update module prometheus/client_golang to v1.1.0 305 | - build: download tools used by TravisCI instead of installing them 306 | 307 | ## v0.2.0 (Released 2019-06-19) 308 | 309 | BUILD 310 | 311 | - Ship old example FED data files in the Docker image. Production deployments need to replace these with updated files from their Financial Institution. 312 | 313 | ## v0.1.x (Released 2019-03-06) 314 | 315 | BUG FIXES 316 | 317 | - Fix automated build steps and Docker setup 318 | 319 | ADDITIONS 320 | 321 | - Added environmental variables for data filepaths 322 | 323 | ## v0.1.0 (Released 2019-03-06) 324 | 325 | - Initial release 326 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at wade@wadearnold.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24 as builder 2 | WORKDIR /go/src/github.com/moov-io/fed 3 | RUN apt-get update && apt-get install make gcc g++ 4 | COPY . . 5 | RUN make build 6 | RUN useradd --shell /bin/false moov 7 | 8 | FROM scratch 9 | LABEL maintainer="Moov " 10 | 11 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 12 | COPY --from=builder /go/src/github.com/moov-io/fed/bin/server /bin/server 13 | COPY --from=builder /etc/passwd /etc/passwd 14 | 15 | COPY data/*.txt /data/fed/ 16 | 17 | ENV FEDACH_DATA_PATH=/data/fed/FedACHdir.txt 18 | ENV FEDWIRE_DATA_PATH=/data/fed/fpddir.txt 19 | 20 | USER moov 21 | EXPOSE 8086 22 | EXPOSE 9096 23 | ENTRYPOINT ["/bin/server"] 24 | -------------------------------------------------------------------------------- /Dockerfile-fedtest: -------------------------------------------------------------------------------- 1 | FROM golang:1.24-alpine as builder 2 | RUN apk add -U make git 3 | RUN adduser -D -g '' --shell /bin/false moov 4 | 5 | # Pull api code into image, then build 6 | WORKDIR /go/src/github.com/moov-io/fed/ 7 | COPY . . 8 | RUN make build 9 | USER moov 10 | 11 | FROM scratch 12 | LABEL maintainer="Moov " 13 | 14 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt 15 | COPY --from=builder /go/src/github.com/moov-io/fed/bin/fedtest /bin/fedtest 16 | COPY --from=builder /etc/passwd /etc/passwd 17 | 18 | USER moov 19 | EXPOSE 8080 20 | EXPOSE 9090 21 | ENTRYPOINT ["/bin/fedtest"] 22 | -------------------------------------------------------------------------------- /Dockerfile-openshift: -------------------------------------------------------------------------------- 1 | # Step one: build scapresults 2 | FROM registry.access.redhat.com/ubi9/go-toolset as builder 3 | COPY go.mod go.mod 4 | COPY go.sum go.sum 5 | COPY *.go ./ 6 | COPY ./cmd/fedtest ./cmd/fedtest 7 | COPY ./cmd/server ./cmd/server 8 | COPY ./client ./client 9 | COPY ./data ./data 10 | COPY ./pkg ./pkg 11 | COPY makefile makefile 12 | RUN make build 13 | 14 | FROM registry.access.redhat.com/ubi9/ubi-minimal 15 | 16 | ARG VERSION=unknown 17 | LABEL maintainer="Moov " 18 | LABEL name="fed" 19 | LABEL version=$VERSION 20 | 21 | COPY --from=builder /opt/app-root/src/bin/server /bin/server 22 | 23 | COPY data/*.txt /data/fed/ 24 | 25 | ENV FEDACH_DATA_PATH=/data/fed/FedACHdir.txt 26 | ENV FEDWIRE_DATA_PATH=/data/fed/fpddir.txt 27 | 28 | EXPOSE 8086 29 | EXPOSE 9096 30 | 31 | ENTRYPOINT ["/bin/server"] 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /api/client.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | description: FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 4 | version: v1 5 | title: FED API 6 | contact: 7 | name: FED API Support 8 | url: 'https://github.com/moov-io/fed' 9 | license: 10 | name: Apache 2.0 11 | url: 'http://www.apache.org/licenses/LICENSE-2.0.html' 12 | servers: 13 | - url: 'http://localhost:8086' 14 | description: Local development 15 | tags: 16 | - name: FED 17 | description: FEDACH Dictionary and FEDWIRE Dictionary 18 | paths: 19 | /ping: 20 | get: 21 | tags: 22 | - FED 23 | summary: Ping the FED service to check if running 24 | operationId: ping 25 | responses: 26 | '200': 27 | description: Service is running properly 28 | content: 29 | text/plain: 30 | example: PONG 31 | /fed/ach/search: 32 | get: 33 | tags: 34 | - FED 35 | summary: Search FEDACH names and metadata 36 | operationId: searchFEDACH 37 | parameters: 38 | - name: X-Request-ID 39 | in: header 40 | description: Optional Request ID allows application developer to trace requests through the systems logs 41 | example: rs4f9915 42 | schema: 43 | type: string 44 | - name: X-User-ID 45 | in: header 46 | description: Optional User ID used to perform this search 47 | schema: 48 | type: string 49 | - name: name 50 | in: query 51 | schema: 52 | type: string 53 | example: Farmers 54 | description: FEDACH Financial Institution Name 55 | - name: routingNumber 56 | in: query 57 | schema: 58 | type: string 59 | example: 044112187 60 | description: FEDACH Routing Number for a Financial Institution 61 | - name: state 62 | in: query 63 | schema: 64 | type: string 65 | example: OH 66 | description: FEDACH Financial Institution State 67 | - name: city 68 | in: query 69 | schema: 70 | type: string 71 | example: CALDWELL 72 | description: FEDACH Financial Institution City 73 | - name: postalCode 74 | in: query 75 | schema: 76 | type: string 77 | example: 43724 78 | description: FEDACH Financial Institution Postal Code 79 | - name: limit 80 | in: query 81 | schema: 82 | type: integer 83 | example: 499 84 | description: Maximum results returned by a search 85 | responses: 86 | '200': 87 | description: FEDACH Participants returned from a search 88 | content: 89 | application/json: 90 | schema: 91 | $ref: '#/components/schemas/ACHDictionary' 92 | '400': 93 | description: Invalid, check error(s). 94 | content: 95 | application/json: 96 | schema: 97 | $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' 98 | '500': 99 | description: Internal error, check error(s) and report the issue. 100 | /fed/wire/search: 101 | get: 102 | tags: 103 | - FED 104 | summary: Search FEDWIRE names and metadata 105 | operationId: searchFEDWIRE 106 | parameters: 107 | - name: X-Request-ID 108 | in: header 109 | description: Optional Request ID allows application developer to trace requests through the systems logs 110 | example: rs4f9915 111 | schema: 112 | type: string 113 | - name: X-User-ID 114 | in: header 115 | description: Optional User ID used to perform this search 116 | schema: 117 | type: string 118 | - name: name 119 | in: query 120 | schema: 121 | type: string 122 | example: MIDWEST 123 | description: FEDWIRE Financial Institution Name 124 | - name: routingNumber 125 | in: query 126 | schema: 127 | type: string 128 | example: 091905114 129 | description: FEDWIRE Routing Number for a Financial Institution 130 | - name: state 131 | in: query 132 | schema: 133 | type: string 134 | example: IA 135 | description: FEDWIRE Financial Institution State 136 | - name: city 137 | in: query 138 | schema: 139 | type: string 140 | example: IOWA CITY 141 | description: FEDWIRE Financial Institution City 142 | - name: limit 143 | in: query 144 | schema: 145 | type: integer 146 | example: 499 147 | description: Maximum results returned by a search 148 | responses: 149 | '200': 150 | description: FEDWIRE Participants returned from a search 151 | content: 152 | application/json: 153 | schema: 154 | $ref: '#/components/schemas/WIREDictionary' 155 | '400': 156 | description: Invalid, check error(s). 157 | content: 158 | application/json: 159 | schema: 160 | $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' 161 | '500': 162 | description: Internal error, check error(s) and report the issue. 163 | 164 | components: 165 | schemas: 166 | ACHDictionary: 167 | description: Search results containing ACHDictionary of Participants 168 | properties: 169 | ACHParticipants: 170 | type: array 171 | items: 172 | $ref: '#/components/schemas/ACHParticipant' 173 | ACHParticipant: 174 | description: ACHParticipant holds a FedACH dir routing record as defined by Fed ACH Format. https://www.frbservices.org/EPaymentsDirectory/achFormat.html 175 | properties: 176 | routingNumber: 177 | type: string 178 | minLength: 9 179 | maxLength: 9 180 | description: The institution's routing number 181 | example: '044112187' 182 | officeCode: 183 | type: string 184 | minLength: 1 185 | maxLength: 1 186 | description: | 187 | Main/Head Office or Branch 188 | 189 | * `O` - Main 190 | * `B` - Branch 191 | enum: 192 | - O 193 | - B 194 | example: 'O' 195 | servicingFRBNumber: 196 | type: string 197 | minLength: 9 198 | maxLength: 9 199 | description: Servicing Fed's main office routing number 200 | example: '041000014' 201 | recordTypeCode: 202 | type: string 203 | minLength: 1 204 | maxLength: 1 205 | description: | 206 | The code indicating the ABA number to be used to route or send ACH items to the RDFI 207 | 208 | * `0` - Institution is a Federal Reserve Bank 209 | * `1` - Send items to customer routing number 210 | * `2` - Send items to customer using new routing number field 211 | enum: 212 | - 0 213 | - 1 214 | - 2 215 | example: '1' 216 | revised: 217 | type: string 218 | maxLength: 8 219 | description: | 220 | Date of last revision 221 | 222 | * YYYYMMDD 223 | * Blank 224 | example: '20190311' 225 | newRoutingNumber: 226 | type: string 227 | minLength: 9 228 | maxLength: 9 229 | description: Financial Institution's new routing number resulting from a merger or renumber 230 | example: '000000000' 231 | customerName: 232 | type: string 233 | maxLength: 36 234 | description: Financial Institution Name 235 | example: FARMERS & MERCHANTS BANK 236 | achLocation: 237 | $ref: '#/components/schemas/ACHLocation' 238 | phoneNumber: 239 | type: string 240 | minLength: 10 241 | maxLength: 10 242 | description: The Financial Institution's phone number 243 | example: '7407325621' 244 | statusCode: 245 | type: string 246 | minLength: 1 247 | maxLength: 1 248 | description: | 249 | Code is based on the customers receiver code 250 | 251 | * `1` - Receives Gov/Comm 252 | enum: 253 | - 1 254 | example: '1' 255 | viewCode: 256 | type: string 257 | minLength: 1 258 | maxLength: 1 259 | description: |- 260 | Code is current view 261 | 262 | * `1` - Current view 263 | enum: 264 | - 1 265 | example: '1' 266 | cleanName: 267 | type: string 268 | description: Normalized name of ACH participant 269 | example: Chase 270 | ACHLocation: 271 | description: ACHLocation is the FEDACH delivery address 272 | properties: 273 | address: 274 | type: string 275 | maxLength: 36 276 | description: Street Address 277 | example: '430 NORTH ST' 278 | city: 279 | type: string 280 | maxLength: 20 281 | description: City 282 | example: 'CALDWELL' 283 | state: 284 | type: string 285 | minLength: 2 286 | maxLength: 2 287 | description: State 288 | example: 'OH' 289 | postalCode: 290 | type: string 291 | minLength: 5 292 | maxLength: 5 293 | description: Postal Code 294 | example: '43724' 295 | postalExtension: 296 | type: string 297 | minLength: 4 298 | maxLength: 4 299 | description: Postal Code Extension 300 | example: '0000' 301 | WIREDictionary: 302 | description: Search results containing WIREDictionary of Participants 303 | properties: 304 | WIREParticipants: 305 | type: array 306 | items: 307 | $ref: '#/components/schemas/WIREParticipant' 308 | WIREParticipant: 309 | description: WIREParticipant holds a FedWIRE dir routing record as defined by Fed WIRE Format. https://frbservices.org/EPaymentsDirectory/fedwireFormat.html 310 | properties: 311 | routingNumber: 312 | type: string 313 | minLength: 9 314 | maxLength: 9 315 | description: The institution's routing number 316 | example: '091905114' 317 | telegraphicName: 318 | type: string 319 | maxLength: 18 320 | description: Short name of financial institution 321 | example: 'MIDWESTONE B&T' 322 | customerName: 323 | type: string 324 | maxLength: 36 325 | description: Financial Institution Name 326 | example: 'MIDWESTONE BK' 327 | wireLocation: 328 | $ref: '#/components/schemas/WIRELocation' 329 | fundsTransferStatus: 330 | type: string 331 | minLength: 1 332 | maxLength: 1 333 | description: | 334 | Designates funds transfer status 335 | 336 | * `Y` - Eligible 337 | * `N` - Ineligible 338 | enum: 339 | - Y 340 | - N 341 | example: 'Y' 342 | fundsSettlementOnlyStatus: 343 | type: string 344 | maxLength: 1 345 | description: | 346 | Designates funds settlement only status 347 | 348 | * `S` - Settlement-Only 349 | enum: 350 | - S 351 | example: '' 352 | bookEntrySecuritiesTransferStatus: 353 | type: string 354 | minLength: 1 355 | maxLength: 1 356 | description: | 357 | Designates book entry securities transfer status 358 | 359 | * `Y` - Eligible 360 | * `N` - Ineligible 361 | enum: 362 | - Y 363 | - N 364 | example: 'N' 365 | date: 366 | type: string 367 | maxLength: 8 368 | description: | 369 | Date of last revision 370 | 371 | * YYYYMMDD 372 | * Blank 373 | example: '20190401' 374 | cleanName: 375 | type: string 376 | description: Normalized name of Wire participant 377 | example: Chase 378 | WIRELocation: 379 | description: WIRELocation is the FEDWIRE delivery address 380 | properties: 381 | city: 382 | type: string 383 | maxLength: 25 384 | description: City 385 | example: 'IOWA CITY' 386 | state: 387 | type: string 388 | minLength: 2 389 | maxLength: 2 390 | description: State 391 | example: 'IA' 392 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /client/.openapi-generator-ignore: -------------------------------------------------------------------------------- 1 | # OpenAPI Generator Ignore 2 | # Generated by openapi-generator https://github.com/openapitools/openapi-generator 3 | 4 | # Use this file to prevent files from being overwritten by the generator. 5 | # The patterns follow closely to .gitignore or .dockerignore. 6 | 7 | # As an example, the C# client generator defines ApiClient.cs. 8 | # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: 9 | #ApiClient.cs 10 | 11 | # You can match any string of characters against a directory, file or extension with a single asterisk (*): 12 | #foo/*/qux 13 | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux 14 | 15 | # You can recursively match patterns against a directory, file or extension with a double asterisk (**): 16 | #foo/**/qux 17 | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux 18 | 19 | # You can also negate patterns with an exclamation (!). 20 | # For example, you can ignore all files in a docs folder with the file extension .md: 21 | #docs/*.md 22 | # Then explicitly reverse the ignore rule for a single file: 23 | #!docs/README.md 24 | -------------------------------------------------------------------------------- /client/.openapi-generator/VERSION: -------------------------------------------------------------------------------- 1 | 4.3.1 -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Go API client for client 2 | 3 | FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 4 | 5 | ## Overview 6 | This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [OpenAPI-spec](https://www.openapis.org/) from a remote server, you can easily generate an API client. 7 | 8 | - API version: v1 9 | - Package version: 1.0.0 10 | - Build package: org.openapitools.codegen.languages.GoClientCodegen 11 | For more information, please visit [https://github.com/moov-io/fed](https://github.com/moov-io/fed) 12 | 13 | ## Installation 14 | 15 | Install the following dependencies: 16 | 17 | ```shell 18 | go get github.com/stretchr/testify/assert 19 | go get golang.org/x/oauth2 20 | go get golang.org/x/net/context 21 | go get github.com/antihax/optional 22 | ``` 23 | 24 | Put the package under your project folder and add the following in import: 25 | 26 | ```golang 27 | import "./client" 28 | ``` 29 | 30 | ## Documentation for API Endpoints 31 | 32 | All URIs are relative to *http://localhost:8086* 33 | 34 | Class | Method | HTTP request | Description 35 | ------------ | ------------- | ------------- | ------------- 36 | *FEDApi* | [**Ping**](docs/FEDApi.md#ping) | **Get** /ping | Ping the FED service to check if running 37 | *FEDApi* | [**SearchFEDACH**](docs/FEDApi.md#searchfedach) | **Get** /fed/ach/search | Search FEDACH names and metadata 38 | *FEDApi* | [**SearchFEDWIRE**](docs/FEDApi.md#searchfedwire) | **Get** /fed/wire/search | Search FEDWIRE names and metadata 39 | 40 | 41 | ## Documentation For Models 42 | 43 | - [AchDictionary](docs/AchDictionary.md) 44 | - [AchLocation](docs/AchLocation.md) 45 | - [AchParticipant](docs/AchParticipant.md) 46 | - [Error](docs/Error.md) 47 | - [Logo](docs/Logo.md) 48 | - [WireDictionary](docs/WireDictionary.md) 49 | - [WireLocation](docs/WireLocation.md) 50 | - [WireParticipant](docs/WireParticipant.md) 51 | 52 | 53 | ## Documentation For Authorization 54 | 55 | Endpoints do not require authorization. 56 | 57 | 58 | 59 | ## Author 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /client/configuration.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | import ( 13 | "fmt" 14 | "net/http" 15 | "strings" 16 | ) 17 | 18 | // contextKeys are used to identify the type of value in the context. 19 | // Since these are string, it is possible to get a short description of the 20 | // context key for logging and debugging using key.String(). 21 | 22 | type contextKey string 23 | 24 | func (c contextKey) String() string { 25 | return "auth " + string(c) 26 | } 27 | 28 | var ( 29 | // ContextOAuth2 takes an oauth2.TokenSource as authentication for the request. 30 | ContextOAuth2 = contextKey("token") 31 | 32 | // ContextBasicAuth takes BasicAuth as authentication for the request. 33 | ContextBasicAuth = contextKey("basic") 34 | 35 | // ContextAccessToken takes a string oauth2 access token as authentication for the request. 36 | ContextAccessToken = contextKey("accesstoken") 37 | 38 | // ContextAPIKey takes an APIKey as authentication for the request 39 | ContextAPIKey = contextKey("apikey") 40 | ) 41 | 42 | // BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth 43 | type BasicAuth struct { 44 | UserName string `json:"userName,omitempty"` 45 | Password string `json:"password,omitempty"` 46 | } 47 | 48 | // APIKey provides API key based authentication to a request passed via context using ContextAPIKey 49 | type APIKey struct { 50 | Key string 51 | Prefix string 52 | } 53 | 54 | // ServerVariable stores the information about a server variable 55 | type ServerVariable struct { 56 | Description string 57 | DefaultValue string 58 | EnumValues []string 59 | } 60 | 61 | // ServerConfiguration stores the information about a server 62 | type ServerConfiguration struct { 63 | Url string 64 | Description string 65 | Variables map[string]ServerVariable 66 | } 67 | 68 | // Configuration stores the configuration of the API client 69 | type Configuration struct { 70 | BasePath string `json:"basePath,omitempty"` 71 | Host string `json:"host,omitempty"` 72 | Scheme string `json:"scheme,omitempty"` 73 | DefaultHeader map[string]string `json:"defaultHeader,omitempty"` 74 | UserAgent string `json:"userAgent,omitempty"` 75 | Debug bool `json:"debug,omitempty"` 76 | Servers []ServerConfiguration 77 | HTTPClient *http.Client 78 | } 79 | 80 | // NewConfiguration returns a new Configuration object 81 | func NewConfiguration() *Configuration { 82 | cfg := &Configuration{ 83 | BasePath: "http://localhost:8086", 84 | DefaultHeader: make(map[string]string), 85 | UserAgent: "OpenAPI-Generator/1.0.0/go", 86 | Debug: false, 87 | Servers: []ServerConfiguration{ 88 | { 89 | Url: "http://localhost:8086", 90 | Description: "Local development", 91 | }, 92 | }, 93 | } 94 | return cfg 95 | } 96 | 97 | // AddDefaultHeader adds a new HTTP header to the default header in the request 98 | func (c *Configuration) AddDefaultHeader(key string, value string) { 99 | c.DefaultHeader[key] = value 100 | } 101 | 102 | // ServerUrl returns URL based on server settings 103 | func (c *Configuration) ServerUrl(index int, variables map[string]string) (string, error) { 104 | if index < 0 || len(c.Servers) <= index { 105 | return "", fmt.Errorf("Index %v out of range %v", index, len(c.Servers)-1) 106 | } 107 | server := c.Servers[index] 108 | url := server.Url 109 | 110 | // go through variables and replace placeholders 111 | for name, variable := range server.Variables { 112 | if value, ok := variables[name]; ok { 113 | found := bool(len(variable.EnumValues) == 0) 114 | for _, enumValue := range variable.EnumValues { 115 | if value == enumValue { 116 | found = true 117 | } 118 | } 119 | if !found { 120 | return "", fmt.Errorf("The variable %s in the server URL has invalid value %v. Must be %v", name, value, variable.EnumValues) 121 | } 122 | url = strings.Replace(url, "{"+name+"}", value, -1) 123 | } else { 124 | url = strings.Replace(url, "{"+name+"}", variable.DefaultValue, -1) 125 | } 126 | } 127 | return url, nil 128 | } 129 | -------------------------------------------------------------------------------- /client/docs/AchDictionary.md: -------------------------------------------------------------------------------- 1 | # AchDictionary 2 | 3 | ## Properties 4 | 5 | Name | Type | Description | Notes 6 | ------------ | ------------- | ------------- | ------------- 7 | **ACHParticipants** | [**[]AchParticipant**](ACHParticipant.md) | | [optional] 8 | 9 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/docs/AchLocation.md: -------------------------------------------------------------------------------- 1 | # AchLocation 2 | 3 | ## Properties 4 | 5 | Name | Type | Description | Notes 6 | ------------ | ------------- | ------------- | ------------- 7 | **Address** | **string** | Street Address | [optional] 8 | **City** | **string** | City | [optional] 9 | **State** | **string** | State | [optional] 10 | **PostalCode** | **string** | Postal Code | [optional] 11 | **PostalExtension** | **string** | Postal Code Extension | [optional] 12 | 13 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 14 | 15 | 16 | -------------------------------------------------------------------------------- /client/docs/AchParticipant.md: -------------------------------------------------------------------------------- 1 | # AchParticipant 2 | 3 | ## Properties 4 | 5 | Name | Type | Description | Notes 6 | ------------ | ------------- | ------------- | ------------- 7 | **RoutingNumber** | **string** | The institution's routing number | [optional] 8 | **OfficeCode** | **string** | Main/Head Office or Branch * `O` - Main * `B` - Branch | [optional] 9 | **ServicingFRBNumber** | **string** | Servicing Fed's main office routing number | [optional] 10 | **RecordTypeCode** | **string** | The code indicating the ABA number to be used to route or send ACH items to the RDFI * `0` - Institution is a Federal Reserve Bank * `1` - Send items to customer routing number * `2` - Send items to customer using new routing number field | [optional] 11 | **Revised** | **string** | Date of last revision * YYYYMMDD * Blank | [optional] 12 | **NewRoutingNumber** | **string** | Financial Institution's new routing number resulting from a merger or renumber | [optional] 13 | **CustomerName** | **string** | Financial Institution Name | [optional] 14 | **AchLocation** | [**AchLocation**](ACHLocation.md) | | [optional] 15 | **PhoneNumber** | **string** | The Financial Institution's phone number | [optional] 16 | **StatusCode** | **string** | Code is based on the customers receiver code * `1` - Receives Gov/Comm | [optional] 17 | **ViewCode** | **string** | Code is current view * `1` - Current view | [optional] 18 | **CleanName** | **string** | Normalized name of ACH participant | [optional] 19 | **Logo** | [**Logo**](Logo.md) | | [optional] 20 | 21 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 22 | 23 | 24 | -------------------------------------------------------------------------------- /client/docs/Error.md: -------------------------------------------------------------------------------- 1 | # Error 2 | 3 | ## Properties 4 | 5 | Name | Type | Description | Notes 6 | ------------ | ------------- | ------------- | ------------- 7 | **Error** | **string** | An error message describing the problem intended for humans. | 8 | 9 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/docs/FEDApi.md: -------------------------------------------------------------------------------- 1 | # \FEDApi 2 | 3 | All URIs are relative to *http://localhost:8086* 4 | 5 | Method | HTTP request | Description 6 | ------------- | ------------- | ------------- 7 | [**Ping**](FEDApi.md#Ping) | **Get** /ping | Ping the FED service to check if running 8 | [**SearchFEDACH**](FEDApi.md#SearchFEDACH) | **Get** /fed/ach/search | Search FEDACH names and metadata 9 | [**SearchFEDWIRE**](FEDApi.md#SearchFEDWIRE) | **Get** /fed/wire/search | Search FEDWIRE names and metadata 10 | 11 | 12 | 13 | ## Ping 14 | 15 | > Ping(ctx, ) 16 | 17 | Ping the FED service to check if running 18 | 19 | ### Required Parameters 20 | 21 | This endpoint does not need any parameter. 22 | 23 | ### Return type 24 | 25 | (empty response body) 26 | 27 | ### Authorization 28 | 29 | No authorization required 30 | 31 | ### HTTP request headers 32 | 33 | - **Content-Type**: Not defined 34 | - **Accept**: text/plain 35 | 36 | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) 37 | [[Back to Model list]](../README.md#documentation-for-models) 38 | [[Back to README]](../README.md) 39 | 40 | 41 | ## SearchFEDACH 42 | 43 | > AchDictionary SearchFEDACH(ctx, optional) 44 | 45 | Search FEDACH names and metadata 46 | 47 | ### Required Parameters 48 | 49 | 50 | Name | Type | Description | Notes 51 | ------------- | ------------- | ------------- | ------------- 52 | **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. 53 | **optional** | ***SearchFEDACHOpts** | optional parameters | nil if no parameters 54 | 55 | ### Optional Parameters 56 | 57 | Optional parameters are passed through a pointer to a SearchFEDACHOpts struct 58 | 59 | 60 | Name | Type | Description | Notes 61 | ------------- | ------------- | ------------- | ------------- 62 | **xRequestID** | **optional.String**| Optional Request ID allows application developer to trace requests through the systems logs | 63 | **xUserID** | **optional.String**| Optional User ID used to perform this search | 64 | **name** | **optional.String**| FEDACH Financial Institution Name | 65 | **routingNumber** | **optional.String**| FEDACH Routing Number for a Financial Institution | 66 | **state** | **optional.String**| FEDACH Financial Institution State | 67 | **city** | **optional.String**| FEDACH Financial Institution City | 68 | **postalCode** | **optional.String**| FEDACH Financial Institution Postal Code | 69 | **limit** | **optional.Int32**| Maximum results returned by a search | 70 | 71 | ### Return type 72 | 73 | [**AchDictionary**](ACHDictionary.md) 74 | 75 | ### Authorization 76 | 77 | No authorization required 78 | 79 | ### HTTP request headers 80 | 81 | - **Content-Type**: Not defined 82 | - **Accept**: application/json 83 | 84 | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) 85 | [[Back to Model list]](../README.md#documentation-for-models) 86 | [[Back to README]](../README.md) 87 | 88 | 89 | ## SearchFEDWIRE 90 | 91 | > WireDictionary SearchFEDWIRE(ctx, optional) 92 | 93 | Search FEDWIRE names and metadata 94 | 95 | ### Required Parameters 96 | 97 | 98 | Name | Type | Description | Notes 99 | ------------- | ------------- | ------------- | ------------- 100 | **ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc. 101 | **optional** | ***SearchFEDWIREOpts** | optional parameters | nil if no parameters 102 | 103 | ### Optional Parameters 104 | 105 | Optional parameters are passed through a pointer to a SearchFEDWIREOpts struct 106 | 107 | 108 | Name | Type | Description | Notes 109 | ------------- | ------------- | ------------- | ------------- 110 | **xRequestID** | **optional.String**| Optional Request ID allows application developer to trace requests through the systems logs | 111 | **xUserID** | **optional.String**| Optional User ID used to perform this search | 112 | **name** | **optional.String**| FEDWIRE Financial Institution Name | 113 | **routingNumber** | **optional.String**| FEDWIRE Routing Number for a Financial Institution | 114 | **state** | **optional.String**| FEDWIRE Financial Institution State | 115 | **city** | **optional.String**| FEDWIRE Financial Institution City | 116 | **limit** | **optional.Int32**| Maximum results returned by a search | 117 | 118 | ### Return type 119 | 120 | [**WireDictionary**](WIREDictionary.md) 121 | 122 | ### Authorization 123 | 124 | No authorization required 125 | 126 | ### HTTP request headers 127 | 128 | - **Content-Type**: Not defined 129 | - **Accept**: application/json 130 | 131 | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) 132 | [[Back to Model list]](../README.md#documentation-for-models) 133 | [[Back to README]](../README.md) 134 | 135 | -------------------------------------------------------------------------------- /client/docs/Logo.md: -------------------------------------------------------------------------------- 1 | # Logo 2 | 3 | ## Properties 4 | 5 | Name | Type | Description | Notes 6 | ------------ | ------------- | ------------- | ------------- 7 | **Name** | **string** | Company name | [optional] 8 | **Url** | **string** | URL to the company logo | [optional] 9 | 10 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/docs/WireDictionary.md: -------------------------------------------------------------------------------- 1 | # WireDictionary 2 | 3 | ## Properties 4 | 5 | Name | Type | Description | Notes 6 | ------------ | ------------- | ------------- | ------------- 7 | **WIREParticipants** | [**[]WireParticipant**](WIREParticipant.md) | | [optional] 8 | 9 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/docs/WireLocation.md: -------------------------------------------------------------------------------- 1 | # WireLocation 2 | 3 | ## Properties 4 | 5 | Name | Type | Description | Notes 6 | ------------ | ------------- | ------------- | ------------- 7 | **City** | **string** | City | [optional] 8 | **State** | **string** | State | [optional] 9 | 10 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 11 | 12 | 13 | -------------------------------------------------------------------------------- /client/docs/WireParticipant.md: -------------------------------------------------------------------------------- 1 | # WireParticipant 2 | 3 | ## Properties 4 | 5 | Name | Type | Description | Notes 6 | ------------ | ------------- | ------------- | ------------- 7 | **RoutingNumber** | **string** | The institution's routing number | [optional] 8 | **TelegraphicName** | **string** | Short name of financial institution | [optional] 9 | **CustomerName** | **string** | Financial Institution Name | [optional] 10 | **WireLocation** | [**WireLocation**](WIRELocation.md) | | [optional] 11 | **FundsTransferStatus** | **string** | Designates funds transfer status * `Y` - Eligible * `N` - Ineligible | [optional] 12 | **FundsSettlementOnlyStatus** | **string** | Designates funds settlement only status * `S` - Settlement-Only | [optional] 13 | **BookEntrySecuritiesTransferStatus** | **string** | Designates book entry securities transfer status * `Y` - Eligible * `N` - Ineligible | [optional] 14 | **Date** | **string** | Date of last revision * YYYYMMDD * Blank | [optional] 15 | **CleanName** | **string** | Normalized name of Wire participant | [optional] 16 | **Logo** | [**Logo**](Logo.md) | | [optional] 17 | 18 | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/model_ach_dictionary.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | // AchDictionary Search results containing ACHDictionary of Participants 13 | type AchDictionary struct { 14 | ACHParticipants []AchParticipant `json:"ACHParticipants,omitempty"` 15 | } 16 | -------------------------------------------------------------------------------- /client/model_ach_location.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | // AchLocation ACHLocation is the FEDACH delivery address 13 | type AchLocation struct { 14 | // Street Address 15 | Address string `json:"address,omitempty"` 16 | // City 17 | City string `json:"city,omitempty"` 18 | // State 19 | State string `json:"state,omitempty"` 20 | // Postal Code 21 | PostalCode string `json:"postalCode,omitempty"` 22 | // Postal Code Extension 23 | PostalExtension string `json:"postalExtension,omitempty"` 24 | } 25 | -------------------------------------------------------------------------------- /client/model_ach_participant.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | // AchParticipant ACHParticipant holds a FedACH dir routing record as defined by Fed ACH Format. https://www.frbservices.org/EPaymentsDirectory/achFormat.html 13 | type AchParticipant struct { 14 | // The institution's routing number 15 | RoutingNumber string `json:"routingNumber,omitempty"` 16 | // Main/Head Office or Branch * `O` - Main * `B` - Branch 17 | OfficeCode string `json:"officeCode,omitempty"` 18 | // Servicing Fed's main office routing number 19 | ServicingFRBNumber string `json:"servicingFRBNumber,omitempty"` 20 | // The code indicating the ABA number to be used to route or send ACH items to the RDFI * `0` - Institution is a Federal Reserve Bank * `1` - Send items to customer routing number * `2` - Send items to customer using new routing number field 21 | RecordTypeCode string `json:"recordTypeCode,omitempty"` 22 | // Date of last revision * YYYYMMDD * Blank 23 | Revised string `json:"revised,omitempty"` 24 | // Financial Institution's new routing number resulting from a merger or renumber 25 | NewRoutingNumber string `json:"newRoutingNumber,omitempty"` 26 | // Financial Institution Name 27 | CustomerName string `json:"customerName,omitempty"` 28 | AchLocation AchLocation `json:"achLocation,omitempty"` 29 | // The Financial Institution's phone number 30 | PhoneNumber string `json:"phoneNumber,omitempty"` 31 | // Code is based on the customers receiver code * `1` - Receives Gov/Comm 32 | StatusCode string `json:"statusCode,omitempty"` 33 | // Code is current view * `1` - Current view 34 | ViewCode string `json:"viewCode,omitempty"` 35 | // Normalized name of ACH participant 36 | CleanName string `json:"cleanName,omitempty"` 37 | Logo Logo `json:"logo,omitempty"` 38 | } 39 | -------------------------------------------------------------------------------- /client/model_error.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | // Error struct for Error 13 | type Error struct { 14 | // An error message describing the problem intended for humans. 15 | Error string `json:"error"` 16 | } 17 | -------------------------------------------------------------------------------- /client/model_logo.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | // Logo Company logo of the Fed ACH or Wire participant 13 | type Logo struct { 14 | // Company name 15 | Name string `json:"name,omitempty"` 16 | // URL to the company logo 17 | Url string `json:"url,omitempty"` 18 | } 19 | -------------------------------------------------------------------------------- /client/model_wire_dictionary.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | // WireDictionary Search results containing WIREDictionary of Participants 13 | type WireDictionary struct { 14 | WIREParticipants []WireParticipant `json:"WIREParticipants,omitempty"` 15 | } 16 | -------------------------------------------------------------------------------- /client/model_wire_location.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | // WireLocation WIRELocation is the FEDWIRE delivery address 13 | type WireLocation struct { 14 | // City 15 | City string `json:"city,omitempty"` 16 | // State 17 | State string `json:"state,omitempty"` 18 | } 19 | -------------------------------------------------------------------------------- /client/model_wire_participant.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | // WireParticipant WIREParticipant holds a FedWIRE dir routing record as defined by Fed WIRE Format. https://frbservices.org/EPaymentsDirectory/fedwireFormat.html 13 | type WireParticipant struct { 14 | // The institution's routing number 15 | RoutingNumber string `json:"routingNumber,omitempty"` 16 | // Short name of financial institution 17 | TelegraphicName string `json:"telegraphicName,omitempty"` 18 | // Financial Institution Name 19 | CustomerName string `json:"customerName,omitempty"` 20 | WireLocation WireLocation `json:"wireLocation,omitempty"` 21 | // Designates funds transfer status * `Y` - Eligible * `N` - Ineligible 22 | FundsTransferStatus string `json:"fundsTransferStatus,omitempty"` 23 | // Designates funds settlement only status * `S` - Settlement-Only 24 | FundsSettlementOnlyStatus string `json:"fundsSettlementOnlyStatus,omitempty"` 25 | // Designates book entry securities transfer status * `Y` - Eligible * `N` - Ineligible 26 | BookEntrySecuritiesTransferStatus string `json:"bookEntrySecuritiesTransferStatus,omitempty"` 27 | // Date of last revision * YYYYMMDD * Blank 28 | Date string `json:"date,omitempty"` 29 | // Normalized name of Wire participant 30 | CleanName string `json:"cleanName,omitempty"` 31 | Logo Logo `json:"logo,omitempty"` 32 | } 33 | -------------------------------------------------------------------------------- /client/response.go: -------------------------------------------------------------------------------- 1 | /* 2 | * FED API 3 | * 4 | * FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 5 | * 6 | * API version: v1 7 | * Generated by: OpenAPI Generator (https://openapi-generator.tech) 8 | */ 9 | 10 | package client 11 | 12 | import ( 13 | "net/http" 14 | ) 15 | 16 | // APIResponse stores the API response returned by the server. 17 | type APIResponse struct { 18 | *http.Response `json:"-"` 19 | Message string `json:"message,omitempty"` 20 | // Operation is the name of the OpenAPI operation. 21 | Operation string `json:"operation,omitempty"` 22 | // RequestURL is the request URL. This value is always available, even if the 23 | // embedded *http.Response is nil. 24 | RequestURL string `json:"url,omitempty"` 25 | // Method is the HTTP method used for the request. This value is always 26 | // available, even if the embedded *http.Response is nil. 27 | Method string `json:"method,omitempty"` 28 | // Payload holds the contents of the response body (which may be nil or empty). 29 | // This is provided here as the raw response.Body() reader will have already 30 | // been drained. 31 | Payload []byte `json:"-"` 32 | } 33 | 34 | // NewAPIResponse returns a new APIResonse object. 35 | func NewAPIResponse(r *http.Response) *APIResponse { 36 | 37 | response := &APIResponse{Response: r} 38 | return response 39 | } 40 | 41 | // NewAPIResponseWithError returns a new APIResponse object with the provided error message. 42 | func NewAPIResponseWithError(errorMessage string) *APIResponse { 43 | 44 | response := &APIResponse{Message: errorMessage} 45 | return response 46 | } 47 | -------------------------------------------------------------------------------- /cmd/fedtest/ach.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | client "github.com/moov-io/fed/client" 12 | 13 | "github.com/antihax/optional" 14 | ) 15 | 16 | func achSearch(api *client.APIClient, requestID, routingNumber string) error { 17 | opts := &client.SearchFEDACHOpts{ 18 | XRequestID: optional.NewString(requestID), 19 | } 20 | if routingNumber != "" { 21 | opts.RoutingNumber = optional.NewString(routingNumber) 22 | } 23 | 24 | dict, resp, err := api.FEDApi.SearchFEDACH(context.Background(), opts) 25 | if err != nil { 26 | return fmt.Errorf("FED ACH error: %v", err) 27 | } 28 | defer resp.Body.Close() 29 | 30 | // Verify the requested routing number was found 31 | for i := range dict.ACHParticipants { 32 | if dict.ACHParticipants[i].RoutingNumber == routingNumber { 33 | return nil 34 | } 35 | } 36 | return fmt.Errorf("FED ACH no participant found for %s", routingNumber) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/fedtest/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | // fedtest is a cli tool for testing Moov's FED API endpoints. 6 | // 7 | // fedtest is not a stable tool. Please contact Moov developers if you intend to use this tool, 8 | // otherwise we might change the tool (or remove it) without notice. 9 | package main 10 | 11 | import ( 12 | "flag" 13 | "fmt" 14 | "log" 15 | "net/http" 16 | "net/url" 17 | "time" 18 | 19 | "github.com/moov-io/base" 20 | "github.com/moov-io/fed" 21 | client "github.com/moov-io/fed/client" 22 | ) 23 | 24 | var ( 25 | flagLocal = flag.Bool("local", false, "Use local HTTP addresses") 26 | flagDebug = flag.Bool("debug", false, "Enable verbose debug logging") 27 | flagAddress = flag.String("address", "https://api.moov.io/v1", "HTTP address for FED service") 28 | 29 | flagRoutingNumber = flag.String("routing-number", chaseCaliforniaRouting, "Routing number to lookup in FED") 30 | ) 31 | 32 | const ( 33 | chaseCaliforniaRouting = "322271627" 34 | ) 35 | 36 | func main() { 37 | flag.Parse() 38 | 39 | log.SetFlags(log.Ldate | log.Ltime | log.LUTC | log.Lmicroseconds | log.Lshortfile) 40 | log.Printf("Starting fedtest %s", fed.Version) 41 | 42 | api := client.NewAPIClient(makeConfig()) 43 | 44 | requestID, routingNumber := base.ID(), *flagRoutingNumber 45 | log.Printf("[INFO] using x-request-id: %s", requestID) 46 | 47 | // ACH search 48 | if err := achSearch(api, requestID, routingNumber); err != nil { 49 | log.Fatalf("[FAILURE] ACH: error looking up %s: %v", routingNumber, err) 50 | } else { 51 | log.Printf("[SUCCESS] ACH: found %s", routingNumber) 52 | } 53 | 54 | // WIRE search 55 | if err := wireSearch(api, requestID, routingNumber); err != nil { 56 | log.Fatalf("[FAILURE] Wire: error looking up %s: %v", routingNumber, err) 57 | } else { 58 | log.Printf("[SUCCESS] Wire: found %s", routingNumber) 59 | } 60 | } 61 | 62 | func makeConfig() *client.Configuration { 63 | conf := client.NewConfiguration() 64 | if *flagAddress != "" { 65 | u, _ := url.Parse(*flagAddress) 66 | conf.Scheme = u.Scheme 67 | conf.Host = u.Host 68 | conf.BasePath = u.Path 69 | } 70 | if *flagLocal { 71 | conf.Scheme = "http" 72 | conf.Host = "localhost:8086" 73 | conf.BasePath = "" 74 | } 75 | if *flagDebug { 76 | conf.Debug = true 77 | } 78 | conf.UserAgent = fmt.Sprintf("moov fedtest/%s", fed.Version) 79 | conf.HTTPClient = &http.Client{ 80 | Timeout: 30 * time.Second, 81 | Transport: &http.Transport{ 82 | MaxIdleConns: 100, 83 | MaxIdleConnsPerHost: 100, 84 | MaxConnsPerHost: 100, 85 | IdleConnTimeout: 1 * time.Minute, 86 | }, 87 | } 88 | return conf 89 | } 90 | -------------------------------------------------------------------------------- /cmd/fedtest/wire.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | 11 | client "github.com/moov-io/fed/client" 12 | 13 | "github.com/antihax/optional" 14 | ) 15 | 16 | func wireSearch(api *client.APIClient, requestID, routingNumber string) error { 17 | opts := &client.SearchFEDWIREOpts{ 18 | XRequestID: optional.NewString(requestID), 19 | } 20 | if routingNumber != "" { 21 | opts.RoutingNumber = optional.NewString(routingNumber) 22 | } 23 | 24 | dict, resp, err := api.FEDApi.SearchFEDWIRE(context.Background(), opts) 25 | if err != nil { 26 | return fmt.Errorf("FED Wire error: %v", err) 27 | } 28 | defer resp.Body.Close() 29 | 30 | // Verify the requested routing number was found 31 | for i := range dict.WIREParticipants { 32 | if dict.WIREParticipants[i].RoutingNumber == routingNumber { 33 | return nil 34 | } 35 | } 36 | return fmt.Errorf("FED Wire no participant found for %s", routingNumber) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/server/http.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "net/http" 10 | "strings" 11 | 12 | moovhttp "github.com/moov-io/base/http" 13 | "github.com/moov-io/base/log" 14 | 15 | "github.com/go-kit/kit/metrics/prometheus" 16 | stdprometheus "github.com/prometheus/client_golang/prometheus" 17 | ) 18 | 19 | var ( 20 | routeHistogram = prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ 21 | Name: "http_response_duration_seconds", 22 | Help: "Histogram representing the http response durations", 23 | }, []string{"route"}) 24 | ) 25 | 26 | func wrapResponseWriter(logger log.Logger, w http.ResponseWriter, r *http.Request) http.ResponseWriter { 27 | route := fmt.Sprintf("%s%s", strings.ToLower(r.Method), strings.Replace(r.URL.Path, "/", "-", -1)) // TODO: filter out random ID's later 28 | return moovhttp.Wrap(logger, routeHistogram.With("route", route), w, r) 29 | } 30 | -------------------------------------------------------------------------------- /cmd/server/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "crypto/tls" 10 | "errors" 11 | "flag" 12 | "fmt" 13 | "io" 14 | "net/http" 15 | "os" 16 | "os/signal" 17 | "strings" 18 | "syscall" 19 | "time" 20 | 21 | "github.com/moov-io/base/admin" 22 | moovhttp "github.com/moov-io/base/http" 23 | "github.com/moov-io/base/http/bind" 24 | "github.com/moov-io/base/log" 25 | "github.com/moov-io/fed" 26 | 27 | "github.com/gorilla/mux" 28 | ) 29 | 30 | var ( 31 | httpAddr = flag.String("http.addr", bind.HTTP("fed"), "HTTP listen address") 32 | adminAddr = flag.String("admin.addr", bind.Admin("fed"), "Admin HTTP listen address") 33 | 34 | flagLogFormat = flag.String("log.format", "", "Format for log lines (Options: json, plain") 35 | ) 36 | 37 | func main() { 38 | flag.Parse() 39 | 40 | var logger log.Logger 41 | if v := os.Getenv("LOG_FORMAT"); v != "" { 42 | *flagLogFormat = v 43 | } 44 | if strings.ToLower(*flagLogFormat) == "json" { 45 | logger = log.NewJSONLogger() 46 | } else { 47 | logger = log.NewDefaultLogger() 48 | } 49 | logger.Info().Logf("Starting fed server version %s", fed.Version) 50 | 51 | // Channel for errors 52 | errs := make(chan error) 53 | 54 | go func() { 55 | c := make(chan os.Signal, 1) 56 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) 57 | errs <- fmt.Errorf("%s", <-c) 58 | }() 59 | 60 | // Setup business HTTP routes 61 | router := mux.NewRouter() 62 | moovhttp.AddCORSHandler(router) 63 | addPingRoute(router) 64 | 65 | // Start business HTTP server 66 | readTimeout, _ := time.ParseDuration("30s") 67 | writTimeout, _ := time.ParseDuration("30s") 68 | idleTimeout, _ := time.ParseDuration("60s") 69 | 70 | // Check to see if our -http.addr flag has been overridden 71 | if v := os.Getenv("HTTP_BIND_ADDRESS"); v != "" { 72 | *httpAddr = v 73 | } 74 | 75 | serve := &http.Server{ 76 | Addr: *httpAddr, 77 | Handler: router, 78 | TLSConfig: &tls.Config{ 79 | InsecureSkipVerify: false, 80 | PreferServerCipherSuites: true, 81 | MinVersion: tls.VersionTLS12, 82 | }, 83 | ReadTimeout: readTimeout, 84 | ReadHeaderTimeout: readTimeout, 85 | WriteTimeout: writTimeout, 86 | IdleTimeout: idleTimeout, 87 | } 88 | shutdownServer := func() { 89 | if err := serve.Shutdown(context.TODO()); err != nil { 90 | logger.Logf("shutting down: %v", err) 91 | } 92 | } 93 | 94 | // Check to see if our -admin.addr flag has been overridden 95 | if v := os.Getenv("HTTP_ADMIN_BIND_ADDRESS"); v != "" { 96 | *adminAddr = v 97 | } 98 | 99 | // Start Admin server (with Prometheus metrics) 100 | adminServer, err := admin.New(admin.Opts{ 101 | Addr: *adminAddr, 102 | }) 103 | if err != nil { 104 | logger.LogErrorf("problem creating admin server: %v", err) 105 | os.Exit(1) 106 | } 107 | adminServer.AddVersionHandler(fed.Version) // Setup 'GET /version' 108 | go func() { 109 | logger.Info().Logf(fmt.Sprintf("listening on %s", adminServer.BindAddr())) 110 | if err := adminServer.Listen(); err != nil { 111 | err = fmt.Errorf("problem starting admin http: %v", err) 112 | logger.Logf("admin: %v", err) 113 | errs <- err 114 | } 115 | }() 116 | defer adminServer.Shutdown() 117 | 118 | // Start our searcher 119 | searcher := &searcher{logger: logger} 120 | 121 | fedACHData, err := fedACHDataFile(logger) 122 | if err != nil { 123 | logger.LogErrorf("problem downloading FedACH: %v", err) 124 | os.Exit(1) 125 | } 126 | fedWireData, err := fedWireDataFile(logger) 127 | if err != nil { 128 | logger.LogErrorf("problem downloading FedWire: %v", err) 129 | os.Exit(1) 130 | } 131 | 132 | if err := setupSearcher(logger, searcher, fedACHData, fedWireData); err != nil { 133 | logger.Logf("read: %v", err) 134 | os.Exit(1) 135 | } 136 | 137 | // Add searcher for HTTP routes 138 | addSearchRoutes(logger, router, searcher) 139 | 140 | // Start business logic HTTP server 141 | go func() { 142 | if certFile, keyFile := os.Getenv("HTTPS_CERT_FILE"), os.Getenv("HTTPS_KEY_FILE"); certFile != "" && keyFile != "" { 143 | logger.Logf("binding to %s for secure HTTP server", *httpAddr) 144 | if err := serve.ListenAndServeTLS(certFile, keyFile); err != nil { 145 | logger.Logf("listen: %v", err) 146 | } 147 | } else { 148 | logger.Logf("binding to %s for HTTP server", *httpAddr) 149 | if err := serve.ListenAndServe(); err != nil { 150 | logger.Logf("listen: %v", err) 151 | } 152 | } 153 | }() 154 | 155 | // Block/Wait for an error 156 | if err := <-errs; err != nil { 157 | shutdownServer() 158 | logger.Logf("exit: %v", err) 159 | os.Exit(1) 160 | } 161 | } 162 | 163 | func addPingRoute(r *mux.Router) { 164 | r.Methods("GET").Path("/ping").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 165 | moovhttp.SetAccessControlAllowHeaders(w, r.Header.Get("Origin")) 166 | w.Header().Set("Content-Type", "text/plain") 167 | w.WriteHeader(http.StatusOK) 168 | w.Write([]byte("PONG")) 169 | }) 170 | } 171 | 172 | func setupSearcher(logger log.Logger, s *searcher, achFile, wireFile io.Reader) error { 173 | if achFile == nil { 174 | return errors.New("missing fedach data file") 175 | } 176 | if wireFile == nil { 177 | return errors.New("missing fedwire data file") 178 | } 179 | 180 | if err := s.readFEDACHData(achFile); err != nil { 181 | return fmt.Errorf("error reading ACH data: %v", err) 182 | } 183 | if err := s.readFEDWIREData(wireFile); err != nil { 184 | return fmt.Errorf("error reading wire data: %v", err) 185 | } 186 | 187 | return s.precompute() 188 | } 189 | -------------------------------------------------------------------------------- /cmd/server/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | 12 | "github.com/moov-io/base/log" 13 | ) 14 | 15 | func TestSearcher__setup(t *testing.T) { 16 | s := &searcher{logger: log.NewNopLogger()} 17 | 18 | logger := log.NewNopLogger() 19 | achFile, err := os.Open(filepath.Join("..", "..", "data", "FedACHdir.txt")) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | wireFile, err := os.Open(filepath.Join("..", "..", "data", "fpddir.txt")) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | if err := setupSearcher(logger, s, achFile, wireFile); err != nil { 29 | t.Fatal(err) 30 | } 31 | if err := setupSearcher(logger, s, achFile, nil); err == nil { 32 | t.Errorf("expected error") 33 | } 34 | if err := setupSearcher(logger, s, nil, nil); err == nil { 35 | t.Errorf("expected error") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cmd/server/reader.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | 15 | "github.com/moov-io/base/log" 16 | "github.com/moov-io/fed" 17 | "github.com/moov-io/fed/pkg/download" 18 | ) 19 | 20 | var ( 21 | fedachFilenames = []string{"FedACHdir.txt", "fedachdir.json", "fedach.txt", "fedach.json"} 22 | fedwireFilenames = []string{"fpddir.json", "fpddir.txt", "fedwire.txt", "fedwire.json"} 23 | ) 24 | 25 | func fedACHDataFile(logger log.Logger) (io.Reader, error) { 26 | initialDir := os.Getenv("INITIAL_DATA_DIRECTORY") 27 | file, err := inspectInitialDataDirectory(logger, initialDir, fedachFilenames) 28 | if err != nil { 29 | return nil, fmt.Errorf("inspecting %s for FedACH file failed: %w", initialDir, err) 30 | } 31 | if file != nil { 32 | logger.Info().Logf("found FedACH file in %s", initialDir) 33 | return file, nil 34 | } 35 | 36 | file, err = attemptFileDownload(logger, "fedach") 37 | if err != nil && !errors.Is(err, download.ErrMissingConfigValue) { 38 | return nil, fmt.Errorf("problem downloading fedach: %v", err) 39 | } 40 | 41 | if file != nil { 42 | logger.Info().Log("search: downloaded ACH file") 43 | return file, nil 44 | } 45 | 46 | path := readDataFilepath("FEDACH_DATA_PATH", "./data/FedACHdir.txt") 47 | logger.Logf("search: loading %s for ACH data", path) 48 | 49 | file, err = os.Open(path) 50 | if err != nil { 51 | return nil, fmt.Errorf("problem opening %s: %v", path, err) 52 | } 53 | return file, nil 54 | } 55 | 56 | func fedWireDataFile(logger log.Logger) (io.Reader, error) { 57 | initialDir := os.Getenv("INITIAL_DATA_DIRECTORY") 58 | file, err := inspectInitialDataDirectory(logger, initialDir, fedwireFilenames) 59 | if err != nil { 60 | return nil, fmt.Errorf("inspecting %s for FedWire file failed: %w", initialDir, err) 61 | } 62 | if file != nil { 63 | logger.Info().Logf("found FedWire file in %s", initialDir) 64 | return file, nil 65 | } 66 | 67 | file, err = attemptFileDownload(logger, "fedwire") 68 | if err != nil && !errors.Is(err, download.ErrMissingConfigValue) { 69 | return nil, fmt.Errorf("problem downloading fedwire: %v", err) 70 | } 71 | 72 | if file != nil { 73 | logger.Info().Log("search: downloaded Wire file") 74 | return file, nil 75 | } 76 | 77 | path := readDataFilepath("FEDWIRE_DATA_PATH", "./data/fpddir.txt") 78 | logger.Logf("search: loading %s for Wire data", path) 79 | 80 | file, err = os.Open(path) 81 | if err != nil { 82 | return nil, fmt.Errorf("problem opening %s: %v", path, err) 83 | } 84 | return file, nil 85 | } 86 | 87 | func inspectInitialDataDirectory(logger log.Logger, dir string, needles []string) (io.Reader, error) { 88 | entries, err := os.ReadDir(dir) 89 | if err != nil { 90 | if os.IsNotExist(err) { 91 | return nil, nil 92 | } 93 | return nil, fmt.Errorf("readdir on %s failed: %w", dir, err) 94 | } 95 | 96 | for _, entry := range entries { 97 | _, filename := filepath.Split(entry.Name()) 98 | 99 | for idx := range needles { 100 | if strings.EqualFold(filename, needles[idx]) { 101 | where := filepath.Join(dir, entry.Name()) 102 | 103 | fd, err := os.Open(where) 104 | if err != nil { 105 | return nil, fmt.Errorf("opening %s failed: %w", where, err) 106 | } 107 | return fd, nil 108 | } 109 | } 110 | } 111 | 112 | return nil, nil 113 | } 114 | 115 | func attemptFileDownload(logger log.Logger, listName string) (io.Reader, error) { 116 | logger.Logf("download: attempting %s", listName) 117 | client, err := download.NewClient(nil) 118 | if err != nil { 119 | return nil, fmt.Errorf("client setup: %w", err) 120 | } 121 | return client.GetList(listName) 122 | } 123 | 124 | func readDataFilepath(env, fallback string) string { 125 | if v := os.Getenv(env); v != "" { 126 | return v 127 | } 128 | return fallback 129 | } 130 | 131 | // readFEDACHData opens and reads FedACHdir.txt then runs ACHDictionary.Read() to 132 | // parse and define ACHDictionary properties 133 | func (s *searcher) readFEDACHData(reader io.Reader) error { 134 | if s.logger != nil { 135 | s.logger.Logf("Read of FED ACH data from %T", reader) 136 | } 137 | 138 | if closer, ok := reader.(io.Closer); ok { 139 | defer closer.Close() 140 | } 141 | 142 | s.ACHDictionary = fed.NewACHDictionary() 143 | if err := s.ACHDictionary.Read(reader); err != nil { 144 | return fmt.Errorf("ERROR: reading FedACHdir.txt %v", err) 145 | } 146 | 147 | recordCount := len(s.ACHDictionary.ACHParticipants) 148 | if recordCount <= 0 { 149 | return errors.New("read zero records from FedACH file") 150 | } else { 151 | if s.logger != nil { 152 | s.logger.With(log.Fields{ 153 | "records": log.Int(recordCount), 154 | }).Logf("Finished refresh of ACH data") 155 | } 156 | } 157 | 158 | return nil 159 | } 160 | 161 | // readFEDWIREData opens and reads fpddir.txt then runs WIREDictionary.Read() to 162 | // parse and define WIREDictionary properties 163 | func (s *searcher) readFEDWIREData(reader io.Reader) error { 164 | if s.logger != nil { 165 | s.logger.Logf("Read of FED Wire data from %T", reader) 166 | } 167 | 168 | if closer, ok := reader.(io.Closer); ok { 169 | defer closer.Close() 170 | } 171 | 172 | s.WIREDictionary = fed.NewWIREDictionary() 173 | if err := s.WIREDictionary.Read(reader); err != nil { 174 | return fmt.Errorf("ERROR: reading fpddir.txt %v", err) 175 | } 176 | 177 | recordCount := len(s.WIREDictionary.WIREParticipants) 178 | if recordCount <= 0 { 179 | return errors.New("read zero records from FedWire file") 180 | } else { 181 | if s.logger != nil { 182 | s.logger.With(log.Fields{ 183 | "records": log.Int(recordCount), 184 | }).Logf("Finished refresh of WIRE data") 185 | } 186 | } 187 | 188 | return nil 189 | } 190 | -------------------------------------------------------------------------------- /cmd/server/reader_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | 12 | "github.com/moov-io/base/log" 13 | 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestReader__fedACHDataFile(t *testing.T) { 18 | t.Setenv("FRB_ROUTING_NUMBER", "") 19 | t.Setenv("FRB_DOWNLOAD_CODE", "") 20 | 21 | r, err := fedACHDataFile(log.NewTestLogger()) 22 | require.Nil(t, r) 23 | require.ErrorContains(t, err, "no such file or directory") 24 | } 25 | 26 | func TestReader__fedWireDataFile(t *testing.T) { 27 | t.Setenv("FRB_ROUTING_NUMBER", "") 28 | t.Setenv("FRB_DOWNLOAD_CODE", "") 29 | 30 | r, err := fedWireDataFile(log.NewTestLogger()) 31 | require.Nil(t, r) 32 | require.ErrorContains(t, err, "no such file or directory") 33 | } 34 | 35 | func TestReader_inspectInitialDataDirectory(t *testing.T) { 36 | logger := log.NewNopLogger() 37 | 38 | dir := t.TempDir() 39 | 40 | err := os.WriteFile(filepath.Join(dir, "fedach.txt"), nil, 0600) 41 | require.NoError(t, err) 42 | err = os.WriteFile(filepath.Join(dir, "fedwire.txt"), nil, 0600) 43 | require.NoError(t, err) 44 | 45 | // FedACH files 46 | fd, err := inspectInitialDataDirectory(logger, dir, fedachFilenames) 47 | require.NoError(t, err) 48 | 49 | file, ok := fd.(*os.File) 50 | require.True(t, ok) 51 | require.Equal(t, filepath.Join(dir, "fedach.txt"), file.Name()) 52 | 53 | // FedWire files 54 | fd, err = inspectInitialDataDirectory(logger, dir, fedwireFilenames) 55 | require.NoError(t, err) 56 | 57 | file, ok = fd.(*os.File) 58 | require.True(t, ok) 59 | require.Equal(t, filepath.Join(dir, "fedwire.txt"), file.Name()) 60 | } 61 | 62 | func TestReader__readFEDACHData(t *testing.T) { 63 | s := &searcher{logger: log.NewNopLogger()} 64 | 65 | achFile, err := os.Open(filepath.Join("..", "..", "data", "FedACHdir.txt")) 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | if err := s.readFEDACHData(achFile); err != nil { 70 | t.Fatal(err) 71 | } 72 | if len(s.ACHDictionary.ACHParticipants) == 0 { 73 | t.Error("no ACH entries parsed") 74 | } 75 | 76 | // bad path 77 | achFile, err = os.Open("reader_test.go") // invalid fedach file 78 | if err != nil { 79 | t.Fatal(err) 80 | } 81 | defer achFile.Close() 82 | if err := s.readFEDACHData(achFile); err == nil { 83 | t.Error("expected error") 84 | } 85 | } 86 | 87 | func TestReader__readFEDWIREData(t *testing.T) { 88 | s := &searcher{logger: log.NewNopLogger()} 89 | 90 | wireFile, err := os.Open(filepath.Join("..", "..", "data", "fpddir.txt")) 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | if err := s.readFEDWIREData(wireFile); err != nil { 95 | t.Fatal(err) 96 | } 97 | if len(s.WIREDictionary.WIREParticipants) == 0 { 98 | t.Error("no Wire entries parsed") 99 | } 100 | 101 | // bad path 102 | wireFile, err = os.Open("reader_test.go") 103 | if err != nil { 104 | t.Fatal(err) 105 | } 106 | defer wireFile.Close() 107 | if err := s.readFEDWIREData(wireFile); err == nil { 108 | t.Error("expected error") 109 | } 110 | } 111 | 112 | func TestReader__readDataFilepath(t *testing.T) { 113 | if v := readDataFilepath("MISSING", "value"); v != "value" { 114 | t.Errorf("got %q", v) 115 | } 116 | if v := readDataFilepath("PATH", "value"); v == "" || v == "value" { 117 | t.Errorf("got %q", v) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /cmd/server/search.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | "net/http" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | 16 | "github.com/moov-io/base/log" 17 | "github.com/moov-io/fed" 18 | ) 19 | 20 | var ( 21 | errNoSearchParams = errors.New("missing search parameter(s)") 22 | softResultsLimit, hardResultsLimit = 100, 500 23 | ) 24 | 25 | // searcher defines a searcher struct 26 | type searcher struct { 27 | ACHDictionary *fed.ACHDictionary 28 | WIREDictionary *fed.WIREDictionary 29 | sync.RWMutex // protects all above fields 30 | 31 | achStats ListStats 32 | wireStats ListStats 33 | 34 | logger log.Logger 35 | } 36 | 37 | type ListStats struct { 38 | Records int `json:"records"` 39 | Latest time.Time `json:"latest"` 40 | } 41 | 42 | func (s *searcher) precompute() error { 43 | if err := s.precomputeACHStats(); err != nil { 44 | return fmt.Errorf("precomputing ACH stats: %w", err) 45 | } 46 | if err := s.precomputeWireStats(); err != nil { 47 | return fmt.Errorf("precomputing wire stats: %w", err) 48 | } 49 | return nil 50 | } 51 | 52 | func (s *searcher) precomputeACHStats() error { 53 | if s.ACHDictionary != nil { 54 | s.achStats.Records = len(s.ACHDictionary.ACHParticipants) 55 | } 56 | 57 | for idx := range s.ACHDictionary.ACHParticipants { 58 | t, err := readDate(s.ACHDictionary.ACHParticipants[idx].Revised) 59 | if err != nil { 60 | return fmt.Errorf("parsing ACH record date: %w", err) 61 | } 62 | if s.achStats.Latest.Before(t) { 63 | s.achStats.Latest = t 64 | } 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (s *searcher) precomputeWireStats() error { 71 | if s.WIREDictionary != nil { 72 | s.wireStats.Records = len(s.WIREDictionary.WIREParticipants) 73 | } 74 | 75 | for idx := range s.WIREDictionary.WIREParticipants { 76 | t, err := readDate(s.WIREDictionary.WIREParticipants[idx].Date) 77 | if err != nil { 78 | return fmt.Errorf("parsing WIRE record date: %w", err) 79 | } 80 | if s.wireStats.Latest.Before(t) { 81 | s.wireStats.Latest = t 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | 88 | var ( 89 | acceptedDateFormats = []string{"060102", "010206", "20060102"} 90 | ) 91 | 92 | func readDate(value string) (tt time.Time, err error) { 93 | value = strings.TrimSpace(value) 94 | if value == "" { 95 | return 96 | } 97 | for _, fmt := range acceptedDateFormats { 98 | tt, err = time.Parse(fmt, value) 99 | if err == nil { 100 | return tt, nil 101 | } 102 | } 103 | return 104 | } 105 | 106 | // searchResponse defines a FEDACH search response 107 | type searchResponse struct { 108 | ACHParticipants []*fed.ACHParticipant `json:"achParticipants,omitempty"` 109 | WIREParticipants []*fed.WIREParticipant `json:"wireParticipants,omitempty"` 110 | 111 | Stats *ListStats `json:"stats"` 112 | } 113 | 114 | // ACHFindNameOnly finds ACH Participants by name only 115 | func (s *searcher) ACHFindNameOnly(limit int, participantName string) []*fed.ACHParticipant { 116 | s.RLock() 117 | defer s.RUnlock() 118 | 119 | return s.ACHDictionary.FinancialInstitutionSearch(participantName, limit) 120 | } 121 | 122 | // ACHFindRoutingNumberOnly finds ACH Participants by routing number only 123 | func (s *searcher) ACHFindRoutingNumberOnly(limit int, routingNumber string) ([]*fed.ACHParticipant, error) { 124 | s.RLock() 125 | defer s.RUnlock() 126 | 127 | return s.ACHDictionary.RoutingNumberSearch(routingNumber, limit) 128 | } 129 | 130 | // ACHFindCityOnly finds ACH Participants by city only 131 | func (s *searcher) ACHFindCityOnly(limit int, city string) []*fed.ACHParticipant { 132 | s.RLock() 133 | defer s.RUnlock() 134 | 135 | return achLimit(s.ACHDictionary.CityFilter(city), limit) 136 | } 137 | 138 | // ACHFindSateOnly finds ACH Participants by state only 139 | func (s *searcher) ACHFindStateOnly(limit int, state string) []*fed.ACHParticipant { 140 | s.RLock() 141 | defer s.RUnlock() 142 | 143 | return achLimit(s.ACHDictionary.StateFilter(state), limit) 144 | } 145 | 146 | // ACHFindPostalCodeOnly finds ACH Participants by postal code only 147 | func (s *searcher) ACHFindPostalCodeOnly(limit int, postalCode string) []*fed.ACHParticipant { 148 | s.RLock() 149 | defer s.RUnlock() 150 | 151 | return achLimit(s.ACHDictionary.PostalCodeFilter(postalCode), limit) 152 | } 153 | 154 | // ACHFind finds ACH Participants based on multiple parameters 155 | func (s *searcher) ACHFind(limit int, req fedSearchRequest) ([]*fed.ACHParticipant, error) { 156 | s.RLock() 157 | defer s.RUnlock() 158 | var err error 159 | 160 | out := s.ACHDictionary.FinancialInstitutionSearch(req.Name, limit) 161 | if req.RoutingNumber != "" { 162 | out, err = s.ACHDictionary.ACHParticipantRoutingNumberFilter(out, req.RoutingNumber) 163 | if err != nil { 164 | return nil, err 165 | } 166 | } 167 | if req.State != "" { 168 | out = s.ACHDictionary.ACHParticipantStateFilter(out, req.State) 169 | } 170 | if req.City != "" { 171 | out = s.ACHDictionary.ACHParticipantCityFilter(out, req.City) 172 | } 173 | if req.PostalCode != "" { 174 | out = s.ACHDictionary.ACHParticipantPostalCodeFilter(out, req.PostalCode) 175 | } 176 | return out, nil 177 | } 178 | 179 | // WIRE Searches 180 | 181 | // WIREFindNameOnly finds WIRE Participants by name only 182 | func (s *searcher) WIREFindNameOnly(limit int, participantName string) []*fed.WIREParticipant { 183 | s.RLock() 184 | defer s.RUnlock() 185 | fi := s.WIREDictionary.FinancialInstitutionSearch(participantName, limit) 186 | out := wireLimit(fi, limit) 187 | return out 188 | } 189 | 190 | // WIREFindRoutingNumberOnly finds WIRE Participants by routing number only 191 | func (s *searcher) WIREFindRoutingNumberOnly(limit int, routingNumber string) ([]*fed.WIREParticipant, error) { 192 | s.RLock() 193 | defer s.RUnlock() 194 | fi, err := s.WIREDictionary.RoutingNumberSearch(routingNumber, limit) 195 | if err != nil { 196 | return nil, err 197 | } 198 | out := wireLimit(fi, limit) 199 | return out, nil 200 | } 201 | 202 | // WIREFindCityOnly finds WIRE Participants by city only 203 | func (s *searcher) WIREFindCityOnly(limit int, city string) []*fed.WIREParticipant { 204 | s.RLock() 205 | defer s.RUnlock() 206 | fi := s.WIREDictionary.CityFilter(city) 207 | out := wireLimit(fi, limit) 208 | return out 209 | } 210 | 211 | // WIREFindSateOnly finds WIRE Participants by state only 212 | func (s *searcher) WIREFindStateOnly(limit int, state string) []*fed.WIREParticipant { 213 | s.RLock() 214 | defer s.RUnlock() 215 | fi := s.WIREDictionary.StateFilter(state) 216 | out := wireLimit(fi, limit) 217 | return out 218 | } 219 | 220 | // WIRE Find finds WIRE Participants based on multiple parameters 221 | func (s *searcher) WIREFind(limit int, req fedSearchRequest) ([]*fed.WIREParticipant, error) { 222 | s.RLock() 223 | defer s.RUnlock() 224 | var err error 225 | fi := s.WIREDictionary.FinancialInstitutionSearch(req.Name, limit) 226 | 227 | if req.RoutingNumber != "" { 228 | fi, err = s.WIREDictionary.WIREParticipantRoutingNumberFilter(fi, req.RoutingNumber) 229 | if err != nil { 230 | return nil, err 231 | } 232 | } 233 | 234 | if req.State != "" { 235 | fi = s.WIREDictionary.WIREParticipantStateFilter(fi, req.State) 236 | } 237 | 238 | if req.City != "" { 239 | fi = s.WIREDictionary.WIREParticipantCityFilter(fi, req.City) 240 | } 241 | 242 | out := wireLimit(fi, limit) 243 | return out, nil 244 | } 245 | 246 | // extractSearchLimit extracts the search limit from url query parameters 247 | func extractSearchLimit(r *http.Request) int { 248 | limit := softResultsLimit 249 | if v := r.URL.Query().Get("limit"); v != "" { 250 | n, _ := strconv.Atoi(v) 251 | if n > 0 { 252 | limit = n 253 | } 254 | } 255 | if limit > hardResultsLimit { 256 | limit = hardResultsLimit 257 | } 258 | return limit 259 | } 260 | 261 | // achLimit returns an FEDACH search result based on the search limit 262 | func achLimit(fi []*fed.ACHParticipant, limit int) []*fed.ACHParticipant { 263 | var out []*fed.ACHParticipant 264 | for _, p := range fi { 265 | if len(out) == limit { 266 | break 267 | } 268 | out = append(out, p) 269 | } 270 | return out 271 | } 272 | 273 | // wireLimit returns a FEDWIRE search result based on the search limit 274 | func wireLimit(fi []*fed.WIREParticipant, limit int) []*fed.WIREParticipant { 275 | var out []*fed.WIREParticipant 276 | for _, p := range fi { 277 | if len(out) == limit { 278 | break 279 | } 280 | out = append(out, p) 281 | } 282 | return out 283 | } 284 | -------------------------------------------------------------------------------- /cmd/server/search_handlers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/json" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | 13 | "github.com/gorilla/mux" 14 | moovhttp "github.com/moov-io/base/http" 15 | "github.com/moov-io/base/log" 16 | "github.com/moov-io/fed" 17 | ) 18 | 19 | func addSearchRoutes(logger log.Logger, r *mux.Router, searcher *searcher) { 20 | r.Methods("GET").Path("/fed/ach/search").HandlerFunc(searchFEDACH(logger, searcher)) 21 | r.Methods("GET").Path("/fed/wire/search").HandlerFunc(searchFEDWIRE(logger, searcher)) 22 | } 23 | 24 | // fedSearchRequest contains the properties for fed ach search request 25 | type fedSearchRequest struct { 26 | Name string `json:"name"` 27 | RoutingNumber string `json:"routingNumber"` 28 | City string `json:"city"` 29 | State string `json:"state"` 30 | PostalCode string `json:"postalCode"` 31 | } 32 | 33 | // readFEDSearchRequest returns a fedachSearchRequest based on url parameters for fed ach search 34 | func readFEDSearchRequest(u *url.URL) fedSearchRequest { 35 | return fedSearchRequest{ 36 | Name: strings.ToUpper(strings.TrimSpace(u.Query().Get("name"))), 37 | RoutingNumber: strings.ToUpper(strings.TrimSpace(u.Query().Get("routingNumber"))), 38 | City: strings.ToUpper(strings.TrimSpace(u.Query().Get("city"))), 39 | State: strings.ToUpper(strings.TrimSpace(u.Query().Get("state"))), 40 | PostalCode: strings.ToUpper(strings.TrimSpace(u.Query().Get("postalCode"))), 41 | } 42 | } 43 | 44 | // empty returns true if all of the properties in fedachSearchRequest are empty 45 | func (req fedSearchRequest) empty() bool { 46 | return req.Name == "" && req.RoutingNumber == "" && req.City == "" && 47 | req.State == "" && req.PostalCode == "" 48 | } 49 | 50 | // nameOnly returns true if only Name is not "" 51 | func (req fedSearchRequest) nameOnly() bool { 52 | return req.Name != "" && req.RoutingNumber == "" && req.City == "" && 53 | req.State == "" && req.PostalCode == "" 54 | } 55 | 56 | // routingNumberOnly returns true if only routingNumber is not "" 57 | func (req fedSearchRequest) routingNumberOnly() bool { 58 | return req.Name == "" && req.RoutingNumber != "" && req.City == "" && 59 | req.State == "" && req.PostalCode == "" 60 | } 61 | 62 | // cityOnly returns true if only city is not "" 63 | func (req fedSearchRequest) cityOnly() bool { 64 | return req.Name == "" && req.RoutingNumber == "" && req.City != "" && 65 | req.State == "" && req.PostalCode == "" 66 | } 67 | 68 | // stateOnly returns true if only state is not "" 69 | func (req fedSearchRequest) stateOnly() bool { 70 | return req.Name == "" && req.RoutingNumber == "" && req.City == "" && 71 | req.State != "" && req.PostalCode == "" 72 | } 73 | 74 | // postalCodeOnly returns true if only postal code is not "" 75 | func (req fedSearchRequest) postalCodeOnly() bool { 76 | return req.Name == "" && req.RoutingNumber == "" && req.City == "" && 77 | req.State == "" && req.PostalCode != "" 78 | } 79 | 80 | // searchFEDACH calls search functions based on the fed ach search request url parameters 81 | func searchFEDACH(logger log.Logger, searcher *searcher) http.HandlerFunc { 82 | return func(w http.ResponseWriter, r *http.Request) { 83 | if logger == nil { 84 | logger = log.NewDefaultLogger() 85 | } 86 | 87 | w = wrapResponseWriter(logger, w, r) 88 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 89 | 90 | requestID, userID := moovhttp.GetRequestID(r), moovhttp.GetUserID(r) 91 | logger = logger.With(log.Fields{ 92 | "requestID": log.String(requestID), 93 | "userID": log.String(userID), 94 | }) 95 | 96 | req := readFEDSearchRequest(r.URL) 97 | if req.empty() { 98 | logger.Error().Logf("searchFedACH", log.String(errNoSearchParams.Error())) 99 | moovhttp.Problem(w, errNoSearchParams) 100 | return 101 | } 102 | 103 | searchLimit := extractSearchLimit(r) 104 | 105 | var achParticipants []*fed.ACHParticipant 106 | var err error 107 | 108 | switch { 109 | case req.nameOnly(): 110 | logger.Logf("searching FED ACH Dictionary by name only %s", req.Name) 111 | achParticipants = searcher.ACHFindNameOnly(searchLimit, req.Name) 112 | 113 | case req.routingNumberOnly(): 114 | logger.Logf("searching FED ACH Dictionary by routing number only %s", req.RoutingNumber) 115 | achParticipants, err = searcher.ACHFindRoutingNumberOnly(searchLimit, req.RoutingNumber) 116 | if err != nil { 117 | moovhttp.Problem(w, err) 118 | return 119 | } 120 | 121 | case req.stateOnly(): 122 | logger.Logf("searching FED ACH Dictionary by state only %s", req.State) 123 | achParticipants = searcher.ACHFindStateOnly(searchLimit, req.State) 124 | 125 | case req.cityOnly(): 126 | logger.Logf("searching FED ACH Dictionary by city only %s", req.City) 127 | achParticipants = searcher.ACHFindCityOnly(searchLimit, req.City) 128 | 129 | case req.postalCodeOnly(): 130 | logger.Logf("searching FED ACH Dictionary by postal code only %s", req.PostalCode) 131 | achParticipants = searcher.ACHFindPostalCodeOnly(searchLimit, req.PostalCode) 132 | 133 | default: 134 | logger.Logf("searching FED ACH Dictionary by parameters %v", req.RoutingNumber) 135 | achParticipants, err = searcher.ACHFind(searchLimit, req) 136 | if err != nil { 137 | moovhttp.Problem(w, err) 138 | return 139 | } 140 | } 141 | 142 | w.WriteHeader(http.StatusOK) 143 | json.NewEncoder(w).Encode(&searchResponse{ 144 | ACHParticipants: achParticipants, 145 | Stats: &searcher.achStats, 146 | }) 147 | } 148 | } 149 | 150 | // searchFEDWIRE calls search functions based on the fed wire search request url parameters 151 | func searchFEDWIRE(logger log.Logger, searcher *searcher) http.HandlerFunc { 152 | return func(w http.ResponseWriter, r *http.Request) { 153 | if logger == nil { 154 | logger = log.NewDefaultLogger() 155 | } 156 | 157 | w = wrapResponseWriter(logger, w, r) 158 | w.Header().Set("Content-Type", "application/json; charset=utf-8") 159 | 160 | requestID, userID := moovhttp.GetRequestID(r), moovhttp.GetUserID(r) 161 | logger = logger.With(log.Fields{ 162 | "requestID": log.String(requestID), 163 | "userID": log.String(userID), 164 | }) 165 | 166 | req := readFEDSearchRequest(r.URL) 167 | if req.empty() { 168 | logger.Error().Logf("searchFEDWIRE: %v", errNoSearchParams) 169 | moovhttp.Problem(w, errNoSearchParams) 170 | return 171 | } 172 | 173 | searchLimit := extractSearchLimit(r) 174 | 175 | var wireParticipants []*fed.WIREParticipant 176 | var err error 177 | 178 | switch { 179 | case req.nameOnly(): 180 | logger.Logf("searchFEDWIRE: searching FED WIRE Dictionary by name only %s", req.Name) 181 | wireParticipants = searcher.WIREFindNameOnly(searchLimit, req.Name) 182 | 183 | case req.routingNumberOnly(): 184 | logger.Logf("searchFEDWIRE: searching FED WIRE Dictionary by routing number only %s", req.RoutingNumber) 185 | wireParticipants, err = searcher.WIREFindRoutingNumberOnly(searchLimit, req.RoutingNumber) 186 | if err != nil { 187 | moovhttp.Problem(w, err) 188 | return 189 | } 190 | 191 | case req.stateOnly(): 192 | logger.Logf("searchFEDWIRE: searching FED WIRE Dictionary by state only %s", req.State) 193 | wireParticipants = searcher.WIREFindStateOnly(searchLimit, req.State) 194 | 195 | case req.cityOnly(): 196 | logger.Logf("searchFEDWIRE: searching FED WIRE Dictionary by city only %s", req.City) 197 | wireParticipants = searcher.WIREFindCityOnly(searchLimit, req.City) 198 | 199 | default: 200 | logger.Logf("searchFEDWIRE: searching FED WIRE Dictionary by parameters %v", req.RoutingNumber) 201 | wireParticipants, err = searcher.WIREFind(searchLimit, req) 202 | if err != nil { 203 | moovhttp.Problem(w, err) 204 | } 205 | } 206 | 207 | w.WriteHeader(http.StatusOK) 208 | json.NewEncoder(w).Encode(&searchResponse{ 209 | WIREParticipants: wireParticipants, 210 | Stats: &searcher.wireStats, 211 | }) 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package fed 6 | 7 | const ( 8 | // ACHLineLength is the FedACH text file line length 9 | ACHLineLength = 155 10 | // WIRELineLength is the FedACH text file line length 11 | WIRELineLength = 101 12 | // MinimumRoutingNumberDigits is the minimum number of digits needed searching by routing numbers 13 | MinimumRoutingNumberDigits = 2 14 | // MaximumRoutingNumberDigits is the maximum number of digits allowed for searching by routing number 15 | // Based on https://www.frbservices.org/EPaymentsDirectory/search.html 16 | MaximumRoutingNumberDigits = 9 17 | ) 18 | -------------------------------------------------------------------------------- /data/fedachdir.json: -------------------------------------------------------------------------------- 1 | { 2 | "fedACHParticipants" : { 3 | "response" : { 4 | "code" : 100 5 | }, 6 | "fedACHParticipants" : [ 7 | { 8 | "routingNumber" : "011000015", 9 | "officeCode" : "O", 10 | "servicingFRBNumber" : "011000015", 11 | "recordTypeCode" : "0", 12 | "changeDate" : "122415", 13 | "newRoutingNumber" : "000000000", 14 | "customerName" : "FEDERAL RESERVE BANK", 15 | "customerAddress" : "1000 PEACHTREE ST N.E.", 16 | "customerCity" : "ATLANTA", 17 | "customerState" : "GA", 18 | "customerZip" : "30309", 19 | "customerZipExt" : "4470", 20 | "customerAreaCode" : "877", 21 | "customerPhonePrefix" : "372", 22 | "customerPhoneSuffix" : "2457", 23 | "institutionStatusCode" : "1", 24 | "dataViewCode" : "1" 25 | }, 26 | { 27 | "routingNumber": "073905527", 28 | "officeCode": "O", 29 | "servicingFRBNumber": "071000301", 30 | "recordTypeCode": "1", 31 | "changeDate": "012908", 32 | "newRoutingNumber": "000000000", 33 | "customerName": "LINCOLN SAVINGS BANK", 34 | "customerAddress": "P O BOX E", 35 | "customerCity": "REINBECK", 36 | "customerState": "IA", 37 | "customerZip": "50669", 38 | "customerZipExt": "0159", 39 | "customerAreaCode" : "319", 40 | "customerPhonePrefix" : "788", 41 | "customerPhoneSuffix" : "6441", 42 | "institutionStatusCode": "1", 43 | "dataViewCode": "1" 44 | }, 45 | { 46 | "routingNumber": "325183657", 47 | "officeCode": "O", 48 | "servicingFRBNumber": "121000374", 49 | "recordTypeCode": "2", 50 | "changeDate": "110118", 51 | "newRoutingNumber": "325182836", 52 | "customerName": "LOWER VALLEY CU", 53 | "customerAddress": "PO BOX 479", 54 | "customerCity": "SUNNYSIDE", 55 | "customerState": "WA", 56 | "customerZip": "98944", 57 | "customerZipExt": "0000", 58 | "customerAreaCode": "509", 59 | "customerPhonePrefix": "837", 60 | "customerPhoneSuffix": "5295", 61 | "institutionStatusCode": "1", 62 | "dataViewCode": "1" 63 | }, 64 | { 65 | "routingNumber": "011000206", 66 | "officeCode": "O", 67 | "servicingFRBNumber": "011000015", 68 | "recordTypeCode": "1", 69 | "changeDate": "072505", 70 | "newRoutingNumber": "000000000", 71 | "customerName": "BANK OF AMERICA N.A", 72 | "customerAddress": "PO BOX 27025", 73 | "customerCity": "RICHMOND", 74 | "customerState": "VA", 75 | "customerZip": "23261", 76 | "customerZipExt": "7025", 77 | "customerAreaCode": "800", 78 | "customerPhonePrefix": "446", 79 | "customerPhoneSuffix": "0135", 80 | "statusCode": "1", 81 | "dataViewCode": "1" 82 | }, 83 | { 84 | "routingNumber": "031207924", 85 | "officeCode": "O", 86 | "servicingFRBNumber": "031000040", 87 | "recordTypeCode": "1", 88 | "changeDate": "112614", 89 | "newRoutingNumber": "000000000", 90 | "customerName": "FIRST BANK", 91 | "customerAddress": "2465 RUSER RD", 92 | "customerCity": "HAMILTON", 93 | "customerState": "NJ", 94 | "customerZip": "08690", 95 | "customerZipExt": "0000", 96 | "customerAreaCode": "609", 97 | "customerPhonePrefix": "643", 98 | "customerPhoneSuffix": "0061", 99 | "statusCode": "1", 100 | "dataViewCode": "1" 101 | }, 102 | { 103 | "routingNumber": "301271787", 104 | "officeCode": "O", 105 | "servicingFRBNumber": "101000048", 106 | "recordTypeCode": "1", 107 | "changeDate": "012114", 108 | "newRoutingNumber": "000000000", 109 | "customerName": "FARMERS STATE BANK", 110 | "customerAddress": "PO BOX 567", 111 | "customerCity": "CAMERON", 112 | "customerState": "MO", 113 | "customerZip": "64429", 114 | "customerZipExt": "0000", 115 | "customerAreaCode": "816", 116 | "customerPhonePrefix": "632", 117 | "customerPhoneSuffix": "6641", 118 | "statusCode": "1", 119 | "dataViewCode": "1" 120 | } 121 | ] 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /data/fpddir.json: -------------------------------------------------------------------------------- 1 | { 2 | "fedwireParticipants" : { 3 | "response" : { 4 | "code" : 100 5 | }, 6 | "fedwireParticipants" : [ 7 | { 8 | "routingNumber" : "011000015", 9 | "telegraphicName" : "FRB-BOS", 10 | "customerName" : "FEDERAL RESERVE BANK OF BOSTON", 11 | "customerState" : "MA", 12 | "customerCity" : "BOSTON", 13 | "fundsEligibility" : "Y", 14 | "fundsSettlementOnlyStatus" : " ", 15 | "securitiesEligibility" : "Y", 16 | "changeDate" : "20040910" 17 | }, 18 | { 19 | "routingNumber": "325280039", 20 | "telegraphicName" : "MAC FCU", 21 | "customerName" : "MAC FEDERAL CREDIT UNION", 22 | "customerState" : "AK", 23 | "customerCity" : "FAIRBANKS", 24 | "fundsEligibility" : "Y", 25 | "fundsSettlementOnlyStatus" : " ", 26 | "securitiesEligibility" : "Y", 27 | "changeDate" : "20180629" 28 | }, 29 | { 30 | "routingNumber": "324172465", 31 | "telegraphicName" : "ALBERT EFCU BOIS", 32 | "customerName" : "TRUGROCER FEDERAL CREDIT UNION", 33 | "customerState" : "ID", 34 | "customerCity" : "BOISE", 35 | "fundsEligibility" : "Y", 36 | "fundsSettlementOnlyStatus" : " ", 37 | "securitiesEligibility" : "N", 38 | "changeDate" : "20061002" 39 | }, 40 | { 41 | "routingNumber": "021000018", 42 | "telegraphicName" : "BK OF NYC", 43 | "customerName" : "THE BANK OF NEW YORK MELLON", 44 | "customerState" : "NY", 45 | "customerCity" : "NEW YORK", 46 | "fundsEligibility" : "Y", 47 | "fundsSettlementOnlyStatus" : " ", 48 | "securitiesEligibility" : "Y", 49 | "changeDate" : "20080701" 50 | }, 51 | { 52 | "routingNumber": "031207924", 53 | "telegraphicName" : "FIRST BANK", 54 | "customerName" : "FIRST BANK", 55 | "customerState" : "NJ", 56 | "customerCity" : "HAMILTON", 57 | "fundsEligibility" : "Y", 58 | "fundsSettlementOnlyStatus" : " ", 59 | "securitiesEligibility" : "N", 60 | "changeDate" : "20180307" 61 | }, 62 | { 63 | "routingNumber": "021054941", 64 | "telegraphicName" : "FARMER MAC SEC P&I", 65 | "customerName" : "FARMER MAC SEC P&I ACCOUNT", 66 | "customerState" : "NJ", 67 | "customerCity" : "EAST RUTHERFORD", 68 | "fundsEligibility" : "Y", 69 | "fundsSettlementOnlyStatus" : " ", 70 | "securitiesEligibility" : "Y", 71 | "changeDate" : "20160331" 72 | }, 73 | { 74 | "routingNumber": "113109898", 75 | "telegraphicName" : "CARMINE STATE BK", 76 | "customerName" : "CARMINE STATE BANK", 77 | "customerState" : "TX", 78 | "customerCity" : "CARMINE", 79 | "fundsEligibility" : "Y", 80 | "fundsSettlementOnlyStatus" : " ", 81 | "securitiesEligibility" : "N", 82 | "changeDate" : " " 83 | }, 84 | { 85 | "routingNumber": "053103640", 86 | "telegraphicName" : "FMB GRANITE QUARRY", 87 | "customerName" : "FARMERS & MERCHANTS BANK", 88 | "customerState" : "NC", 89 | "customerCity" : "SALISBURY", 90 | "fundsEligibility" : "Y", 91 | "fundsSettlementOnlyStatus" : " ", 92 | "securitiesEligibility" : "Y", 93 | "changeDate" : "20070504" 94 | }, 95 | { 96 | "routingNumber": "011075150", 97 | "telegraphicName" : "SANTANDER BK", 98 | "customerName" : "SANTANDER BANK, N.A.", 99 | "customerState" : "PA", 100 | "customerCity" : "WYOMISSING", 101 | "fundsEligibility" : "Y", 102 | "fundsSettlementOnlyStatus" : " ", 103 | "securitiesEligibility" : "Y", 104 | "changeDate" : "20131029" 105 | }, 106 | { 107 | "routingNumber": "211372404", 108 | "telegraphicName" : "READING COOP BANK", 109 | "customerName" : "READING CO-OPERATIVE BANK", 110 | "customerState" : "MA", 111 | "customerCity" : "READING", 112 | "fundsEligibility" : "Y", 113 | "fundsSettlementOnlyStatus" : " ", 114 | "securitiesEligibility" : "Y", 115 | "changeDate" : "19991230" 116 | } 117 | ] 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | watchman: 4 | image: moov/fed:latest 5 | ports: 6 | - "8086:8086" 7 | - "9096:9096" 8 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs/FEDACHDIR_FORMAT.MD: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: FedACH format 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | 10 | # FedACH directory file format 11 | 12 | **Source:** [achFormat](https://frbservices.org/EPaymentsDirectory/achFormat.html) 13 | 14 | | Field Name | Length | Position | Description | 15 | | --- | --- | --- | --- | 16 | | Routing Number | 9 | 1-9 | The institution's routing number | 17 | | Office Code | 1 | 10 | Main office or branch O=main B=branch | 18 | | Servicing FRB Number | 9 | 11-19 | Servicing Fed's main office routing number | 19 | | Record Type Code | 1 | 20 | The code indicating the ABA number to be used to route or send ACH items to the RFI
0 = Institution is a Federal Reserve Bank
1 = Send items to customer routing number
2 = Send items to customer using new routing number field | 20 | | Change Date | 6 | 21-26 | Date of last change to CRF information (MMDDYY) | 21 | | New Routing Number | 9 | 27-35 | Institution's new routing number resulting from a merger or renumber | 22 | | Customer Name | 36 | 36-71 | Commonly used abbreviated name | 23 | | Address | 36 | 72-107 | Delivery address | 24 | | [City](https://frbservices.org/EPaymentsDirectory/fedachCities.html) | 20 | 108-127 | City name in the delivery address | 25 | | [State Code](Fed_STATE_CODES.md) | 2 | 128-129 | State code of the state in the delivery address | 26 | | Zipcode | 5 | 130-134 | Zipcode in the delivery address | 27 | | Zipcode Extension | 4 | 135-138 | Zipcode extension in the delivery address | 28 | | Telephone Area Code | 3 | 139-141 | Area code of the CRF contact telephone number | 29 | | Telephone Prefix Number | 3 | 142-144 | Prefix of the CRF contact telephone number | 30 | | Telephone Suffix Number | 4 | 145-148 | Suffix of the CRF contact telephone number | 31 | | Institution Status Code | 1 | 149 | Code is based on the customers receiver code
1 = Receives Gov/Comm | 32 | | Data View Code | 1 | 150 | 1 = Current view | 33 | | Filler | 5 | 151-155 | Spaces | -------------------------------------------------------------------------------- /docs/FedACHdir.md: -------------------------------------------------------------------------------- 1 | # FedACH directory 2 | 3 | * Data: [FedACHdir.txt](../data/FedACHdir.txt) 4 | * JSON Data Example: [fedachdir.json](../data/fedachdir.json) 5 | * Format: [FEDACHDIR_FORMAT.MD](FEDACHDIR_FORMAT.MD) 6 | * Source: [Federal Reserve Bank Services](https://frbservices.org/) 7 | 8 | The effective date of this FedACH directory is Dec 4, 2018. These data files are no longer published on the [FRBServices](https://frbservices.org/) website. 9 | -------------------------------------------------------------------------------- /docs/Fed_STATE_CODES.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Fed state codes 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # State and territory abbreviations 10 | 11 | **Source:** [Fed States](https://frbservices.org/EPaymentsDirectory/states.html) 12 | 13 | | Abbreviation | State | 14 | | --- | --- | 15 | | AL | Alabama | 16 | | AK | Alaska | 17 | | AZ | Arizona | 18 | | AR | Arkansas | 19 | | CA | California | 20 | | CO | Colorado | 21 | | CT | Connecticut | 22 | | DE | Delaware | 23 | | DC | District Of Columbia | 24 | | FL | Florida | 25 | | GA | Georgia | 26 | | GU | Guam | 27 | | HI | Hawaii | 28 | | ID | Idaho | 29 | | IL | Illinois | 30 | | IN | Indiana | 31 | | IA | Iowa | 32 | | KS | Kansas | 33 | | KY | Kentucky | 34 | | LA | Louisiana | 35 | | ME | Maine | 36 | | MD | Maryland | 37 | | MA | Massachusetts | 38 | | MI | Michigan | 39 | | MN | Minnesota | 40 | | MS | Mississippi | 41 | | MO | Missouri | 42 | | MT | Montana | 43 | | NE | Nebraska | 44 | | NV | Nevada | 45 | | NH | New Hampshire | 46 | | NJ | New Jersey | 47 | | NM | New Mexico | 48 | | NY | New York | 49 | | NC | North Carolina | 50 | | ND | North Dakota | 51 | | MP | Northern Mariana Islands | 52 | | OH | Ohio | 53 | | OK | Oklahoma | 54 | | OR | Oregon | 55 | | PA | Pennsylvania | 56 | | PR | Puerto Rico | 57 | | RI | Rhode Island | 58 | | SC | South Carolina | 59 | | SD | South Dakota | 60 | | TN | Tennessee | 61 | | TX | Texas | 62 | | UT | Utah | 63 | | VT | Vermont | 64 | | VI | Virgin Islands | 65 | | VA | Virginia | 66 | | WA | Washington | 67 | | WV | West Virginia | 68 | | WI | Wisconsin | 69 | | WY | Wyoming | 70 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 11 | gem "bulma-clean-theme" 12 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 13 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 14 | gem "github-pages", group: :jekyll_plugins 15 | # If you have any plugins, put them here! 16 | group :jekyll_plugins do 17 | gem "jekyll-feed", "~> 0.12" 18 | end 19 | 20 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem 21 | # and associated library. 22 | platforms :mingw, :x64_mingw, :mswin, :jruby do 23 | gem "tzinfo", "~> 1.2" 24 | gem "tzinfo-data" 25 | end 26 | 27 | # Performance-booster for watching directories on Windows 28 | gem "wdm", "~> 0.2.0", :platforms => [:mingw, :x64_mingw, :mswin] 29 | 30 | -------------------------------------------------------------------------------- /docs/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (7.2.0) 5 | base64 6 | bigdecimal 7 | concurrent-ruby (~> 1.0, >= 1.3.1) 8 | connection_pool (>= 2.2.5) 9 | drb 10 | i18n (>= 1.6, < 2) 11 | logger (>= 1.4.2) 12 | minitest (>= 5.1) 13 | securerandom (>= 0.3) 14 | tzinfo (~> 2.0, >= 2.0.5) 15 | addressable (2.8.7) 16 | public_suffix (>= 2.0.2, < 7.0) 17 | base64 (0.2.0) 18 | bigdecimal (3.1.8) 19 | bulma-clean-theme (0.14.0) 20 | jekyll (>= 3.9, < 5.0) 21 | jekyll-feed (~> 0.15) 22 | jekyll-paginate (~> 1.1) 23 | jekyll-seo-tag (~> 2.7) 24 | jekyll-sitemap (~> 1.4) 25 | kramdown-parser-gfm (~> 1.1) 26 | coffee-script (2.4.1) 27 | coffee-script-source 28 | execjs 29 | coffee-script-source (1.12.2) 30 | colorator (1.1.0) 31 | commonmarker (0.23.10) 32 | concurrent-ruby (1.3.4) 33 | connection_pool (2.4.1) 34 | csv (3.3.0) 35 | dnsruby (1.72.2) 36 | simpleidn (~> 0.2.1) 37 | drb (2.2.1) 38 | em-websocket (0.5.3) 39 | eventmachine (>= 0.12.9) 40 | http_parser.rb (~> 0) 41 | ethon (0.16.0) 42 | ffi (>= 1.15.0) 43 | eventmachine (1.2.7) 44 | execjs (2.9.1) 45 | faraday (2.10.1) 46 | faraday-net_http (>= 2.0, < 3.2) 47 | logger 48 | faraday-net_http (3.1.1) 49 | net-http 50 | ffi (1.17.0-arm64-darwin) 51 | ffi (1.17.0-x86_64-darwin) 52 | ffi (1.17.0-x86_64-linux-gnu) 53 | forwardable-extended (2.6.0) 54 | gemoji (4.1.0) 55 | github-pages (232) 56 | github-pages-health-check (= 1.18.2) 57 | jekyll (= 3.10.0) 58 | jekyll-avatar (= 0.8.0) 59 | jekyll-coffeescript (= 1.2.2) 60 | jekyll-commonmark-ghpages (= 0.5.1) 61 | jekyll-default-layout (= 0.1.5) 62 | jekyll-feed (= 0.17.0) 63 | jekyll-gist (= 1.5.0) 64 | jekyll-github-metadata (= 2.16.1) 65 | jekyll-include-cache (= 0.2.1) 66 | jekyll-mentions (= 1.6.0) 67 | jekyll-optional-front-matter (= 0.3.2) 68 | jekyll-paginate (= 1.1.0) 69 | jekyll-readme-index (= 0.3.0) 70 | jekyll-redirect-from (= 0.16.0) 71 | jekyll-relative-links (= 0.6.1) 72 | jekyll-remote-theme (= 0.4.3) 73 | jekyll-sass-converter (= 1.5.2) 74 | jekyll-seo-tag (= 2.8.0) 75 | jekyll-sitemap (= 1.4.0) 76 | jekyll-swiss (= 1.0.0) 77 | jekyll-theme-architect (= 0.2.0) 78 | jekyll-theme-cayman (= 0.2.0) 79 | jekyll-theme-dinky (= 0.2.0) 80 | jekyll-theme-hacker (= 0.2.0) 81 | jekyll-theme-leap-day (= 0.2.0) 82 | jekyll-theme-merlot (= 0.2.0) 83 | jekyll-theme-midnight (= 0.2.0) 84 | jekyll-theme-minimal (= 0.2.0) 85 | jekyll-theme-modernist (= 0.2.0) 86 | jekyll-theme-primer (= 0.6.0) 87 | jekyll-theme-slate (= 0.2.0) 88 | jekyll-theme-tactile (= 0.2.0) 89 | jekyll-theme-time-machine (= 0.2.0) 90 | jekyll-titles-from-headings (= 0.5.3) 91 | jemoji (= 0.13.0) 92 | kramdown (= 2.4.0) 93 | kramdown-parser-gfm (= 1.1.0) 94 | liquid (= 4.0.4) 95 | mercenary (~> 0.3) 96 | minima (= 2.5.1) 97 | nokogiri (>= 1.16.2, < 2.0) 98 | rouge (= 3.30.0) 99 | terminal-table (~> 1.4) 100 | webrick (~> 1.8) 101 | github-pages-health-check (1.18.2) 102 | addressable (~> 2.3) 103 | dnsruby (~> 1.60) 104 | octokit (>= 4, < 8) 105 | public_suffix (>= 3.0, < 6.0) 106 | typhoeus (~> 1.3) 107 | html-pipeline (2.14.3) 108 | activesupport (>= 2) 109 | nokogiri (>= 1.4) 110 | http_parser.rb (0.8.0) 111 | i18n (1.14.5) 112 | concurrent-ruby (~> 1.0) 113 | jekyll (3.10.0) 114 | addressable (~> 2.4) 115 | colorator (~> 1.0) 116 | csv (~> 3.0) 117 | em-websocket (~> 0.5) 118 | i18n (>= 0.7, < 2) 119 | jekyll-sass-converter (~> 1.0) 120 | jekyll-watch (~> 2.0) 121 | kramdown (>= 1.17, < 3) 122 | liquid (~> 4.0) 123 | mercenary (~> 0.3.3) 124 | pathutil (~> 0.9) 125 | rouge (>= 1.7, < 4) 126 | safe_yaml (~> 1.0) 127 | webrick (>= 1.0) 128 | jekyll-avatar (0.8.0) 129 | jekyll (>= 3.0, < 5.0) 130 | jekyll-coffeescript (1.2.2) 131 | coffee-script (~> 2.2) 132 | coffee-script-source (~> 1.12) 133 | jekyll-commonmark (1.4.0) 134 | commonmarker (~> 0.22) 135 | jekyll-commonmark-ghpages (0.5.1) 136 | commonmarker (>= 0.23.7, < 1.1.0) 137 | jekyll (>= 3.9, < 4.0) 138 | jekyll-commonmark (~> 1.4.0) 139 | rouge (>= 2.0, < 5.0) 140 | jekyll-default-layout (0.1.5) 141 | jekyll (>= 3.0, < 5.0) 142 | jekyll-feed (0.17.0) 143 | jekyll (>= 3.7, < 5.0) 144 | jekyll-gist (1.5.0) 145 | octokit (~> 4.2) 146 | jekyll-github-metadata (2.16.1) 147 | jekyll (>= 3.4, < 5.0) 148 | octokit (>= 4, < 7, != 4.4.0) 149 | jekyll-include-cache (0.2.1) 150 | jekyll (>= 3.7, < 5.0) 151 | jekyll-mentions (1.6.0) 152 | html-pipeline (~> 2.3) 153 | jekyll (>= 3.7, < 5.0) 154 | jekyll-optional-front-matter (0.3.2) 155 | jekyll (>= 3.0, < 5.0) 156 | jekyll-paginate (1.1.0) 157 | jekyll-readme-index (0.3.0) 158 | jekyll (>= 3.0, < 5.0) 159 | jekyll-redirect-from (0.16.0) 160 | jekyll (>= 3.3, < 5.0) 161 | jekyll-relative-links (0.6.1) 162 | jekyll (>= 3.3, < 5.0) 163 | jekyll-remote-theme (0.4.3) 164 | addressable (~> 2.0) 165 | jekyll (>= 3.5, < 5.0) 166 | jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) 167 | rubyzip (>= 1.3.0, < 3.0) 168 | jekyll-sass-converter (1.5.2) 169 | sass (~> 3.4) 170 | jekyll-seo-tag (2.8.0) 171 | jekyll (>= 3.8, < 5.0) 172 | jekyll-sitemap (1.4.0) 173 | jekyll (>= 3.7, < 5.0) 174 | jekyll-swiss (1.0.0) 175 | jekyll-theme-architect (0.2.0) 176 | jekyll (> 3.5, < 5.0) 177 | jekyll-seo-tag (~> 2.0) 178 | jekyll-theme-cayman (0.2.0) 179 | jekyll (> 3.5, < 5.0) 180 | jekyll-seo-tag (~> 2.0) 181 | jekyll-theme-dinky (0.2.0) 182 | jekyll (> 3.5, < 5.0) 183 | jekyll-seo-tag (~> 2.0) 184 | jekyll-theme-hacker (0.2.0) 185 | jekyll (> 3.5, < 5.0) 186 | jekyll-seo-tag (~> 2.0) 187 | jekyll-theme-leap-day (0.2.0) 188 | jekyll (> 3.5, < 5.0) 189 | jekyll-seo-tag (~> 2.0) 190 | jekyll-theme-merlot (0.2.0) 191 | jekyll (> 3.5, < 5.0) 192 | jekyll-seo-tag (~> 2.0) 193 | jekyll-theme-midnight (0.2.0) 194 | jekyll (> 3.5, < 5.0) 195 | jekyll-seo-tag (~> 2.0) 196 | jekyll-theme-minimal (0.2.0) 197 | jekyll (> 3.5, < 5.0) 198 | jekyll-seo-tag (~> 2.0) 199 | jekyll-theme-modernist (0.2.0) 200 | jekyll (> 3.5, < 5.0) 201 | jekyll-seo-tag (~> 2.0) 202 | jekyll-theme-primer (0.6.0) 203 | jekyll (> 3.5, < 5.0) 204 | jekyll-github-metadata (~> 2.9) 205 | jekyll-seo-tag (~> 2.0) 206 | jekyll-theme-slate (0.2.0) 207 | jekyll (> 3.5, < 5.0) 208 | jekyll-seo-tag (~> 2.0) 209 | jekyll-theme-tactile (0.2.0) 210 | jekyll (> 3.5, < 5.0) 211 | jekyll-seo-tag (~> 2.0) 212 | jekyll-theme-time-machine (0.2.0) 213 | jekyll (> 3.5, < 5.0) 214 | jekyll-seo-tag (~> 2.0) 215 | jekyll-titles-from-headings (0.5.3) 216 | jekyll (>= 3.3, < 5.0) 217 | jekyll-watch (2.2.1) 218 | listen (~> 3.0) 219 | jemoji (0.13.0) 220 | gemoji (>= 3, < 5) 221 | html-pipeline (~> 2.2) 222 | jekyll (>= 3.0, < 5.0) 223 | kramdown (2.4.0) 224 | rexml 225 | kramdown-parser-gfm (1.1.0) 226 | kramdown (~> 2.0) 227 | liquid (4.0.4) 228 | listen (3.9.0) 229 | rb-fsevent (~> 0.10, >= 0.10.3) 230 | rb-inotify (~> 0.9, >= 0.9.10) 231 | logger (1.6.0) 232 | mercenary (0.3.6) 233 | minima (2.5.1) 234 | jekyll (>= 3.5, < 5.0) 235 | jekyll-feed (~> 0.9) 236 | jekyll-seo-tag (~> 2.1) 237 | minitest (5.24.1) 238 | net-http (0.4.1) 239 | uri 240 | nokogiri (1.18.8-arm64-darwin) 241 | racc (~> 1.4) 242 | nokogiri (1.18.8-x86_64-darwin) 243 | racc (~> 1.4) 244 | nokogiri (1.18.8-x86_64-linux-gnu) 245 | racc (~> 1.4) 246 | octokit (4.25.1) 247 | faraday (>= 1, < 3) 248 | sawyer (~> 0.9) 249 | pathutil (0.16.2) 250 | forwardable-extended (~> 2.6) 251 | public_suffix (5.1.1) 252 | racc (1.8.1) 253 | rb-fsevent (0.11.2) 254 | rb-inotify (0.11.1) 255 | ffi (~> 1.0) 256 | rexml (3.3.9) 257 | rouge (3.30.0) 258 | rubyzip (2.3.2) 259 | safe_yaml (1.0.5) 260 | sass (3.7.4) 261 | sass-listen (~> 4.0.0) 262 | sass-listen (4.0.0) 263 | rb-fsevent (~> 0.9, >= 0.9.4) 264 | rb-inotify (~> 0.9, >= 0.9.7) 265 | sawyer (0.9.2) 266 | addressable (>= 2.3.5) 267 | faraday (>= 0.17.3, < 3) 268 | securerandom (0.3.1) 269 | simpleidn (0.2.3) 270 | terminal-table (1.8.0) 271 | unicode-display_width (~> 1.1, >= 1.1.1) 272 | typhoeus (1.4.1) 273 | ethon (>= 0.9.0) 274 | tzinfo (2.0.6) 275 | concurrent-ruby (~> 1.0) 276 | unicode-display_width (1.8.0) 277 | uri (0.13.2) 278 | webrick (1.8.2) 279 | 280 | PLATFORMS 281 | universal-darwin-20 282 | universal-darwin-22 283 | x86_64-linux 284 | 285 | DEPENDENCIES 286 | bulma-clean-theme 287 | github-pages 288 | jekyll-feed (~> 0.12) 289 | tzinfo (~> 1.2) 290 | tzinfo-data 291 | wdm (~> 0.2.0) 292 | 293 | BUNDLED WITH 294 | 2.2.17 295 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Moov Fed 2 | 3 | **[Documentation](https://moov-io.github.io/fed)** | **[Source](https://github.com/moov-io/fed)** | **[Running](https://github.com/moov-io/fed#usage)** | **[Configuration](https://github.com/moov-io/fed#configuration-settings)** 4 | 5 | ### Purpose 6 | 7 | [Moov Fed](https://github.com/moov-io/fed) implements an HTTP interface to search [Fedwire](https://github.com/moov-io/fed/tree/master/docs/fpddir.md) and [FedACH](https://github.com/moov-io/fed/tree/master/docs/FedACHdir.md) data from the Federal Reserve Bank Services. 8 | 9 | The data and formats below represent a compilation of **Fedwire** and **FedACH** data from the [Federal Reserve Bank Services site](https://frbservices.org/): 10 | 11 | * [FEDACH](https://github.com/moov-io/fed/tree/master/docs/FedACHdir.md) 12 | 13 | * [FEDWire](https://github.com/moov-io/fed/tree/master/docs/fpddir.md) 14 | 15 | Fed can be used standalone to search for routing numbers by Financial Institution name, city, state, postal code, and routing number. It can also be used in conjunction with [ACH](https://github.com/moov-io/ach) and [WIRE](https://github.com/moov-io/wire) to validate routing numbers. 16 | 17 | ## FED Data Files 18 | 19 | The data files included in this repository ([`FedACHdir.md`](FedACHdir.md) and [`fpddir.md`](fpddir.md)) are **outdated** and from 2018. The Fed no longer releases this data publicly and licensing on more recent files prevents us from distributing them. However, the Fed still complies this data and you can retrieve up-to-date files for use in our project, either from [LexisNexis](https://risk.lexisnexis.com/financial-services/payments-efficiency/payment-routing) or your financial institution. 20 | 21 | Moov Fed can read the data files from anywhere on the filesystem. This allows you to mount the files and set `FEDACH_DATA_PATH` / `FEDWIRE_DATA_PATH` environmental variables. Both official formats from the Federal Reserve (plaintext and JSON) are supported. 22 | 23 | ### Copyright and Terms of Use 24 | 25 | Copyright © Federal Reserve Banks 26 | 27 | By accessing the [data](https://github.com/moov-io/fed/tree/master/data) in this repository you agree to the [Federal Reserve Banks' Terms of Use](https://frbservices.org/terms/index.html) and the [E-Payments Routing Directory Terms of Use Agreement](https://www.frbservices.org/EPaymentsDirectory/agreement.html). 28 | 29 | ## Disclaimer 30 | 31 | **THIS REPOSITORY IS NOT AFFILIATED WITH THE FEDERAL RESERVE BANKS AND IS NOT AN OFFICIAL SOURCE FOR FEDWIRE AND FEDACH DATA.** 32 | 33 | ## Getting Help 34 | 35 | channel | info 36 | ------- | ------- 37 | [Project Documentation](https://moov-io.github.io/fed/) | Our project documentation available online. 38 | Twitter [@moov](https://twitter.com/moov) | You can follow Moov.io's Twitter feed to get updates on our project(s). You can also tweet us questions or just share blogs or stories. 39 | [GitHub Issue](https://github.com/moov-io/fed/issues) | If you are able to reproduce a problem please open a GitHub Issue under the specific project that caused the error. 40 | [moov-io slack](https://slack.moov.io/) | Join our slack channel (`#fed`) to have an interactive discussion about the development of the project. 41 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | # 11 | # If you need help with YAML syntax, here are some quick references for you: 12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml 13 | # https://learnxinyminutes.com/docs/yaml/ 14 | # 15 | # Site settings 16 | # These are used to personalize your new site. If you look in the HTML files, 17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 18 | # You can create any custom variable you would like, and they will be accessible 19 | # in the templates via {{ site.myvariable }}. 20 | 21 | title: Moov Fed 22 | email: oss@moov.io 23 | description: >- # this means to ignore newlines until "baseurl:" 24 | Fed implements utility services for searching the United States Federal Reserve System such as ABA routing numbers, financial institution name lookup, and Fedwire and FedACH routing information. 25 | url: "https://moov-io.github.io" # the base hostname & protocol for your site, e.g. http://example.com 26 | baseurl: "/fed" # the subpath of your site, e.g. /blog 27 | source_code: https://github.com/moov-io/fed 28 | permalink: pretty 29 | 30 | # Build settings 31 | remote_theme: moov-io/bulma-clean-theme 32 | plugins: 33 | - jekyll-feed 34 | - github-pages 35 | 36 | # Exclude from processing. 37 | # The following items will not be processed, by default. 38 | # Any item listed under the `exclude:` key here will be automatically added to 39 | # the internal "default list". 40 | # 41 | # Excluded items can be processed by explicitly listing the directories or 42 | # their entries' file path in the `include:` list. 43 | # 44 | # exclude: 45 | # - .sass-cache/ 46 | # - .jekyll-cache/ 47 | # - gemfiles/ 48 | # - Gemfile 49 | # - Gemfile.lock 50 | # - node_modules/ 51 | # - vendor/bundle/ 52 | # - vendor/cache/ 53 | # - vendor/gems/ 54 | # - vendor/ruby/ 55 | -------------------------------------------------------------------------------- /docs/_data/docs-menu.yml: -------------------------------------------------------------------------------- 1 | - label: Getting started 2 | items: 3 | - name: Overview 4 | link: / 5 | - name: What is Moov Fed? 6 | link: /intro/ 7 | 8 | - label: Usage 9 | items: 10 | - name: Docker 11 | link: /usage-docker/ 12 | - name: Google Cloud Run 13 | link: /usage-google-cloud/ 14 | - name: Binary distribution 15 | link: /usage-binary/ 16 | - name: API configuration 17 | link: /usage-configuration/ 18 | - name: Go library 19 | link: /usage-go/ 20 | 21 | - label: Fed search 22 | items: 23 | - name: FedACH format 24 | link: /FEDACHDIR_FORMAT/ 25 | - name: Fedwire format 26 | link: /fpddir_FORMAT/ 27 | - name: Fed state codes 28 | link: /Fed_STATE_CODES/ 29 | 30 | 31 | - label: Production and monitoring 32 | items: 33 | - name: Kubernetes 34 | link: /kubernetes/ 35 | - name: Prometheus metrics 36 | link: /prometheus/ -------------------------------------------------------------------------------- /docs/_data/navigation.yml: -------------------------------------------------------------------------------- 1 | - name: API 2 | link: https://moov-io.github.io/fed/api 3 | - name: Go 4 | link: https://pkg.go.dev/github.com/moov-io/fed#section-documentation 5 | - name: Community 6 | dropdown: 7 | - name: Awesome Fintech 8 | link: https://github.com/moov-io/awesome-fintech 9 | - name: Slack 10 | link: https://slack.moov.io/ 11 | - name: Terms Dictionary 12 | link: https://github.com/moov-io/terms-dictionary 13 | - name: Other Projects 14 | dropdown: 15 | - name: Moov ACH 16 | link: https://moov-io.github.io/ach/ 17 | - name: Moov ACHGateway 18 | link: https://moov-io.github.io/achgateway/ 19 | - name: Moov ACH Test Harness 20 | link: https://github.com/moov-io/ach-test-harness 21 | - name: Moov FinCEN 22 | link: https://moov-io.github.io/fincen/ 23 | - name: Moov Image Cash Letter 24 | link: https://moov-io.github.io/imagecashletter/ 25 | - name: Moov Metro 2 26 | link: https://moov-io.github.io/metro2/ 27 | - name: Moov Watchman 28 | link: https://moov-io.github.io/watchman/ 29 | - name: Moov Wire 30 | link: https://moov-io.github.io/wire/ 31 | -------------------------------------------------------------------------------- /docs/api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Moov Fed Endpoints 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moov-io/fed/620886317f1ace16cdbb577c7a4eebc35ce60707/docs/favicon.png -------------------------------------------------------------------------------- /docs/file-structure.md: -------------------------------------------------------------------------------- 1 | # FedACH directory file format 2 | 3 | **Source:** [achFormat](https://frbservices.org/EPaymentsDirectory/achFormat.html) 4 | 5 | | Field Name | Length | Position | Description | 6 | | --- | --- | --- | --- | 7 | | Routing Number | 9 | 1-9 | The institution's routing number | 8 | | Office Code | 1 | 10 | Main office or branch O=main B=branch | 9 | | Servicing FRB Number | 9 | 11-19 | Servicing Fed's main office routing number | 10 | | Record Type Code | 1 | 20 | The code indicating the ABA number to be used to route or send ACH items to the RFI
0 = Institution is a Federal Reserve Bank
1 = Send items to customer routing number
2 = Send items to customer using new routing number field | 11 | | Change Date | 6 | 21-26 | Date of last change to CRF information (MMDDYY) | 12 | | New Routing Number | 9 | 27-35 | Institution's new routing number resulting from a merger or renumber | 13 | | Customer Name | 36 | 36-71 | Commonly used abbreviated name | 14 | | Address | 36 | 72-107 | Delivery address | 15 | | City| 20 | 108-127 | City name in the delivery address | 16 | | State | 2 | 128-129 | State code of the state in the delivery address | 17 | | Zipcode | 5 | 130-134 | Zipcode in the delivery address | 18 | | Zipcode Extension | 4 | 135-138 | Zipcode extension in the delivery address | 19 | | Telephone Area Code | 3 | 139-141 | Area code of the CRF contact telephone number | 20 | | Telephone Prefix Number | 3 | 142-144 | Prefix of the CRF contact telephone number | 21 | | Telephone Suffix Number | 4 | 145-148 | Suffix of the CRF contact telephone number | 22 | | Institution Status Code | 1 | 149 | Code is based on the customers receiver code
1 = Receives Gov/Comm | 23 | | Data View Code | 1 | 150 | 1 = Current view | 24 | | Filler | 5 | 151-155 | Spaces | 25 | 26 | # Fedwire directory file format 27 | 28 | **Source:** [FedWireFormat](https://frbservices.org/EPaymentsDirectory/fedwireFormat.html) 29 | 30 | | Field Name | Length | Columns | 31 | | --- | --- | --- | 32 | | Routing Number | 9 | 1-9 | 33 | | Telegraphic Name | 18 | 10-27 | 34 | | Customer Name | 36 | 28-63 | 35 | | State | 2 | 64-65 | 36 | | City | 25 | 66-90 | 37 | | Funds transfer status:
Y - Eligible
N - Ineligible | 1 | 91 | 38 | | Funds settlement-only status:
S - Settlement-Only | 1 | 92 | 39 | | Book-Entry Securities transfer status:
Y - Eligible
N - Ineligible | 1 | 93 | 40 | | Date of last revision: YYYYMMDD, or blank | 8 | 94-101 | 41 | 42 | -------------------------------------------------------------------------------- /docs/fpddir.md: -------------------------------------------------------------------------------- 1 | # FedWire directory 2 | 3 | * Data: [fpddir.txt](../data/fpddir.txt) 4 | * JSON Data Example: [fpddir.json](../data/fpddir.json) 5 | * Format: [fpddir_format.md](fpddir_FORMAT.md) 6 | * Source: [Federal Reserve Bank Services](https://frbservices.org/) 7 | 8 | The effective date of this Fedwire directory is Dec 4th, 2018. These data files are no longer published on the [FRBServices](https://frbservices.org/) website. -------------------------------------------------------------------------------- /docs/fpddir_FORMAT.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Fedwire format 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Fedwire directory file format 10 | 11 | **Source:** [FedWireFormat](https://frbservices.org/EPaymentsDirectory/fedwireFormat.html) 12 | 13 | | Field Name | Length | Columns | 14 | | --- | --- | --- | 15 | | Routing Number | 9 | 1-9 | 16 | | Telegraphic Name | 18 | 10-27 | 17 | | Customer Name | 36 | 28-63 | 18 | | [State or territory abbreviation](Fed_STATE_CODES.md) | 2 | 64-65 | 19 | | [City](https://frbservices.org/EPaymentsDirectory/fedwireCities.html) | 25 | 66-90 | 20 | | Funds transfer status:
Y - Eligible
N - Ineligible | 1 | 91 | 21 | | Funds settlement-only status:
S - Settlement-Only | 1 | 92 | 22 | | Book-Entry Securities transfer status:
Y - Eligible
N - Ineligible | 1 | 93 | 23 | | Date of last revision: YYYYMMDD, or blank | 8 | 94-101 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Overview 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Overview 10 | 11 | ![Moov Fed Logo](https://user-images.githubusercontent.com/20115216/107704449-176ca600-6c72-11eb-8963-45bd9ae3e557.jpg) 12 | 13 | Moov's mission is to give developers an easy way to create and integrate bank processing into their own software products. Our open source projects are each focused on solving a single responsibility in financial services and designed around performance, scalability, and ease of use. 14 | 15 | Fed implements utility services for searching the United States Federal Reserve System such as [ABA routing numbers](https://en.wikipedia.org/wiki/ABA_routing_transit_number), financial institution name lookup, and [Fedwire](https://en.wikipedia.org/wiki/Fedwire) and [FedACH](https://en.wikipedia.org/wiki/FedACH) routing information. The HTTP server is available in a [Docker image](#docker) and the Go package `github.com/moov-io/fed` is available. Moov's primary usage for this project is with ACH origination in our [paygate](https://github.com/moov-io/paygate) project. 16 | 17 | The data and formats in this repository represent a compilation of **FedWire** and **FedACH** data from the [Federal Reserve Bank Services site](https://frbservices.org/). Both the official Fed plaintext and JSON file formats are supported. 18 | 19 | ## Copyright and terms of use 20 | 21 | Copyright © Federal Reserve Banks 22 | 23 | By accessing the [data](https://github.com/moov-io/fed/tree/master/data) in this repository you agree to the [Federal Reserve Banks' Terms of Use](https://frbservices.org/terms/index.html) and the [E-Payments Routing Directory Terms of Use Agreement](https://www.frbservices.org/EPaymentsDirectory/agreement.html). 24 | 25 | ## Disclaimer 26 | 27 | **THIS REPOSITORY IS NOT AFFILIATED WITH THE FEDERAL RESERVE BANKS AND IS NOT AN OFFICIAL SOURCE FOR FEDWIRE AND FEDACH DATA.** -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Intro 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | ## What is Moov Fed? 10 | 11 | [Moov Fed](https://github.com/moov-io/fed) implements an HTTP interface to search [Fedwire](https://github.com/moov-io/fed/tree/master/docs/fpddir.md) and [FedACH](https://github.com/moov-io/fed/tree/master/docs/FedACHdir.md) data from the Federal Reserve Bank Services. 12 | 13 | The data and formats below represent a compilation of **Fedwire** and **FedACH** data from the [Federal Reserve Bank Services site](https://frbservices.org/): 14 | 15 | * [FEDACH](https://github.com/moov-io/fed/tree/master/docs/FedACHdir.md) 16 | 17 | * [FEDWire](https://github.com/moov-io/fed/tree/master/docs/fpddir.md) 18 | 19 | Fed can be used standalone to search for routing numbers by Financial Institution name, city, state, postal code, and routing number. It can also be used in conjunction with [Moov ACH](https://github.com/moov-io/ach) and [Moov Wire](https://github.com/moov-io/wire) to validate routing numbers. 20 | 21 | ## Data files 22 | 23 | The data files included in this repository ([`FedACHdir.md`](https://github.com/moov-io/fed/tree/master/docs/FedACHdir.md) and [`fpddir.md`](https://github.com/moov-io/fed/tree/master/docs/fpddir.md)) are **outdated** and from 2018. The Fed no longer releases this data publicly and licensing on more recent files prevents us from distributing them. However, the Fed still complies this data and you can retrieve up-to-date files for use in our project, either from [LexisNexis](https://risk.lexisnexis.com/financial-services/payments-efficiency/payment-routing) or your financial institution. 24 | 25 | Moov Fed can read the data files from anywhere on the filesystem. This allows you to mount the files and set `FEDACH_DATA_PATH` / `FEDWIRE_DATA_PATH` environmental variables. Both official formats from the Federal Reserve (plaintext and JSON) are supported. 26 | -------------------------------------------------------------------------------- /docs/kubernetes.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Kubernetes 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Kubernetes 10 | 11 | The following snippet runs the Fed Server on [Kubernetes](https://kubernetes.io/docs/tutorials/kubernetes-basics/) in the `apps` namespace. You can reach the fed instance at the following URL from inside the cluster. 12 | 13 | ``` 14 | # Needs to be ran from inside the cluster 15 | $ curl http://fed.apps.svc.cluster.local:8086/ping 16 | PONG 17 | ``` 18 | 19 | Kubernetes manifest - save in a file (`fed.yaml`) and apply with `kubectl apply -f fed.yaml`. -------------------------------------------------------------------------------- /docs/prometheus.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Prometheus metrics 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Metrics 10 | 11 | The port `9096` is bound by Fed for our admin service. This HTTP server has endpoints for Prometheus metrics (`GET /metrics`), readiness checks (`GET /ready`), and liveness checks (`GET /live`). -------------------------------------------------------------------------------- /docs/usage-binary.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Binary distribution 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Binary distribution 10 | 11 | Download the [latest Moov Fed server release](https://github.com/moov-io/fed/releases) for your operating system and run it from a terminal. 12 | 13 | ```sh 14 | $ ./fed-darwin-amd64 15 | ts=2019-06-20T23:23:44.870717Z caller=main.go:75 startup="Starting fed server version v0.4.1" 16 | ts=2019-06-20T23:23:44.871623Z caller=main.go:135 transport=HTTP addr=:8086 17 | ts=2019-06-20T23:23:44.871692Z caller=main.go:125 admin="listening on :9096" 18 | ``` 19 | 20 | The Moov Fed service will be running on port `8086` (with an admin port on `9096`). 21 | 22 | Confirm that the service is running by issuing the following command or simply visiting [localhost:8086/ping](http://localhost:8086/ping) in your browser. 23 | 24 | ```sh 25 | $ curl http://localhost:8086/ping 26 | PONG 27 | ``` 28 | 29 | Search for a routing number: 30 | 31 | ``` 32 | $ curl "localhost:8086/fed/ach/search?routingNumber=273976369" 33 | { 34 | "achParticipants": [ 35 | { 36 | "routingNumber": "273976369", 37 | "officeCode": "O", 38 | "servicingFRBNumber": "071000301", 39 | "recordTypeCode": "1", 40 | "revised": "041513", 41 | "newRoutingNumber": "000000000", 42 | "customerName": "VERIDIAN CREDIT UNION", 43 | "achLocation": { 44 | "address": "1827 ANSBOROUGH", 45 | "city": "WATERLOO", 46 | "state": "IA", 47 | "postalCode": "50702", 48 | "postalCodeExtension": "0000" 49 | }, 50 | "phoneNumber": "3192878332", 51 | "statusCode": "1", 52 | "viewCode": "1" 53 | } 54 | ], 55 | "wireParticipants": null 56 | } 57 | ``` 58 | 59 | Search for a financial institution by name: 60 | 61 | ``` 62 | $ curl "localhost:8086/fed/ach/search?name=Veridian&limit=1" 63 | { 64 | "achParticipants": [ 65 | { 66 | "routingNumber": "273976369", 67 | "officeCode": "O", 68 | "servicingFRBNumber": "071000301", 69 | "recordTypeCode": "1", 70 | "revised": "041513", 71 | "newRoutingNumber": "000000000", 72 | "customerName": "VERIDIAN CREDIT UNION", 73 | "achLocation": { 74 | "address": "1827 ANSBOROUGH", 75 | "city": "WATERLOO", 76 | "state": "IA", 77 | "postalCode": "50702", 78 | "postalCodeExtension": "0000" 79 | }, 80 | "phoneNumber": "3192878332", 81 | "statusCode": "1", 82 | "viewCode": "1" 83 | } 84 | ], 85 | "wireParticipants": null 86 | } 87 | ``` -------------------------------------------------------------------------------- /docs/usage-configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: API configuration 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Configuration settings 10 | 11 | | Environmental Variable | Description | Default | 12 | |-----|-----|-----| 13 | | `FEDACH_DATA_PATH` | Filepath to FedACH data file | `./data/FedACHdir.txt` | 14 | | `FEDWIRE_DATA_PATH` | Filepath to Fedwire data file | `./data/fpddir.txt` | 15 | | `LOG_FORMAT` | Format for logging lines to be written as. | Options: `json`, `plain` - Default: `plain` | 16 | | `HTTP_BIND_ADDRESS` | Address for Fed to bind its HTTP server on. This overrides the command-line flag `-http.addr`. | Default: `:8086` | 17 | | `HTTP_ADMIN_BIND_ADDRESS` | Address for Fed to bind its admin HTTP server on. This overrides the command-line flag `-admin.addr`. | Default: `:9096` | 18 | | `HTTPS_CERT_FILE` | Filepath containing a certificate (or intermediate chain) to be served by the HTTP server. Requires all traffic be over secure HTTP. | Empty | 19 | | `HTTPS_KEY_FILE` | Filepath of a private key matching the leaf certificate from `HTTPS_CERT_FILE`. | Empty | 20 | 21 | ## Data persistence 22 | By design, Fed **does not persist** (save) any data about the search queries created. The only storage occurs in memory of the process and upon restart Fed will have no files or data saved. Also, no in-memory encryption of the data is performed. -------------------------------------------------------------------------------- /docs/usage-docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Docker 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Docker 10 | 11 | We publish a [public Docker image `moov/fed`](https://hub.docker.com/r/moov/fed/) from Docker Hub or use this repository. No configuration is required to serve on `:8086` and metrics at `:9096/metrics` in Prometheus format. We also have Docker images for [OpenShift](https://quay.io/repository/moov/fed?tab=tags) published as `quay.io/moov/fed`. 12 | 13 | Moov ImageCashLetter is dependent on Docker being properly installed and running on your machine. Ensure that Docker is running. If your Docker client has issues connecting to the service, review the [Docker getting started guide](https://docs.docker.com/get-started/). 14 | 15 | ``` 16 | docker ps 17 | ``` 18 | ``` 19 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 20 | ``` 21 | 22 | Pull & start the Docker image: 23 | ``` 24 | docker pull moov/fed:latest 25 | docker run -p 8086:8086 -p 9096:9096 moov/fed:latest 26 | ``` 27 | 28 | #### **ACH routing number example** 29 | 30 | Fed can be used to look up Financial Institutions for Automated Clearing House ([ACH](https://en.wikipedia.org/wiki/Automated_Clearing_House)) transfers by their routing number (`?routingNumber=...`): 31 | 32 | ``` 33 | curl "localhost:8086/fed/ach/search?routingNumber=273976369" 34 | ``` 35 | ``` 36 | { 37 | "achParticipants": [ 38 | { 39 | "routingNumber": "273976369", 40 | "officeCode": "O", 41 | "servicingFRBNumber": "071000301", 42 | "recordTypeCode": "1", 43 | "revised": "041513", 44 | "newRoutingNumber": "000000000", 45 | "customerName": "VERIDIAN CREDIT UNION", 46 | "achLocation": { 47 | "address": "1827 ANSBOROUGH", 48 | "city": "WATERLOO", 49 | "state": "IA", 50 | "postalCode": "50702", 51 | "postalCodeExtension": "0000" 52 | }, 53 | "phoneNumber": "3192878332", 54 | "statusCode": "1", 55 | "viewCode": "1" 56 | } 57 | ], 58 | "wireParticipants": null 59 | } 60 | ``` 61 | 62 | #### **Wire routing number example** 63 | 64 | Fed can be used to look up Financial Institutions for [Fedwire](https://en.wikipedia.org/wiki/Fedwire) messages by their routing number (`?routingNumber=...`): 65 | 66 | ``` 67 | curl "localhost:8086/fed/wire/search?routingNumber=273976369" 68 | ``` 69 | ``` 70 | { 71 | "achParticipants": null, 72 | "wireParticipants": [ 73 | { 74 | "routingNumber": "273976369", 75 | "telegraphicName": "VERIDIAN", 76 | "customerName": "VERIDIAN CREDIT UNION", 77 | "wireLocation": { 78 | "city": "WATERLOO", 79 | "state": "IA" 80 | }, 81 | "fundsTransferStatus": "Y", 82 | "fundsSettlementOnlyStatus": " ", 83 | "bookEntrySecuritiesTransferStatus": "N", 84 | "date": "20141107" 85 | } 86 | ] 87 | } 88 | ``` -------------------------------------------------------------------------------- /docs/usage-go.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Go library 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Go library 10 | 11 | This project uses [Go Modules](https://go.dev/blog/using-go-modules) and Go v1.18 or newer. See [Golang's install instructions](https://golang.org/doc/install) for help setting up Go. You can download the source code and we offer [tagged and released versions](https://github.com/moov-io/fed/releases/latest) as well. We highly recommend you use a tagged release for production. 12 | 13 | ``` 14 | $ git@github.com:moov-io/fed.git 15 | 16 | # Pull down into the Go Module cache 17 | $ go get -u github.com/moov-io/fed 18 | 19 | $ go doc github.com/moov-io/fed ACHDictionary 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/usage-google-cloud.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Google Cloud Run 4 | hide_hero: true 5 | show_sidebar: false 6 | menubar: docs-menu 7 | --- 8 | 9 | # Google Cloud Run 10 | 11 | To get started in a hosted environment you can deploy this project to the Google Cloud Platform. 12 | 13 | From your [Google Cloud dashboard](https://console.cloud.google.com/home/dashboard) create a new project and call it: 14 | ``` 15 | moov-fed-demo 16 | ``` 17 | 18 | Enable the [Container Registry](https://cloud.google.com/container-registry) API for your project and associate a [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account) if needed. Then, open the Cloud Shell terminal and run the following Docker commands, substituting your unique project ID: 19 | 20 | ``` 21 | docker pull moov/fed 22 | docker tag moov/fed gcr.io//fed 23 | docker push gcr.io//fed 24 | ``` 25 | 26 | Deploy the container to Cloud Run: 27 | ``` 28 | gcloud run deploy --image gcr.io//fed --port 8086 29 | ``` 30 | 31 | Select your target platform to `1`, service name to `fed`, and region to the one closest to you (enable Google API service if a prompt appears). Upon a successful build you will be given a URL where the API has been deployed: 32 | 33 | ``` 34 | https://YOUR-FED-APP-URL.a.run.app 35 | ``` 36 | 37 | Now you can ping the server: 38 | ``` 39 | curl https://YOUR-FED-APP-URL.a.run.app/ping 40 | ``` 41 | You should get this response: 42 | ``` 43 | PONG 44 | ``` -------------------------------------------------------------------------------- /fileErrors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package fed 6 | 7 | import ( 8 | "errors" 9 | "fmt" 10 | ) 11 | 12 | // ErrFileTooLong is the error given when a file exceeds the maximum possible length 13 | var ( 14 | ErrFileTooLong = errors.New("file exceeds maximum possible number of lines") 15 | // Similar to FEDACH site 16 | ErrRoutingNumberNumeric = errors.New("the routing number entered is not numeric") 17 | ) 18 | 19 | // RecordWrongLengthErr is the error given when a record is the wrong length 20 | type RecordWrongLengthErr struct { 21 | Message string 22 | LengthRequired int 23 | Length int 24 | } 25 | 26 | // NewRecordWrongLengthErr creates a new error of the RecordWrongLengthErr type 27 | func NewRecordWrongLengthErr(lengthRequired int, length int) RecordWrongLengthErr { 28 | return RecordWrongLengthErr{ 29 | Message: fmt.Sprintf("must be %d characters and found %d", lengthRequired, length), 30 | LengthRequired: lengthRequired, 31 | Length: length, 32 | } 33 | } 34 | 35 | func (e RecordWrongLengthErr) Error() string { 36 | return e.Message 37 | } 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/moov-io/fed 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.4 6 | 7 | require ( 8 | github.com/antihax/optional v1.0.0 9 | github.com/docker/docker v28.2.2+incompatible 10 | github.com/go-kit/kit v0.13.0 11 | github.com/gorilla/mux v1.8.1 12 | github.com/moov-io/base v0.55.1 13 | github.com/prometheus/client_golang v1.22.0 14 | github.com/stretchr/testify v1.10.0 15 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 16 | golang.org/x/oauth2 v0.30.0 17 | golang.org/x/text v0.26.0 18 | ) 19 | 20 | require ( 21 | github.com/beorn7/perks v1.0.1 // indirect 22 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/go-kit/log v0.2.1 // indirect 25 | github.com/go-logfmt/logfmt v0.6.0 // indirect 26 | github.com/kr/text v0.2.0 // indirect 27 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 28 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 29 | github.com/prometheus/client_model v0.6.1 // indirect 30 | github.com/prometheus/common v0.62.0 // indirect 31 | github.com/prometheus/procfs v0.15.1 // indirect 32 | github.com/rickar/cal/v2 v2.1.23 // indirect 33 | golang.org/x/sys v0.33.0 // indirect 34 | google.golang.org/protobuf v1.36.6 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= 2 | github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= 3 | github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= 4 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 5 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 8 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 10 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g= 13 | github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 14 | github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= 15 | github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 16 | github.com/docker/docker v28.2.0+incompatible h1:UT6N8HqGInwXfM2CDbzUTV5wyzPZmOcEdXiz8/T04vI= 17 | github.com/docker/docker v28.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 18 | github.com/docker/docker v28.2.1+incompatible h1:aTSWVTDStpHbnRu0xBcGoJEjRf5EQKt6nik6Vif8sWw= 19 | github.com/docker/docker v28.2.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 20 | github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= 21 | github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 22 | github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= 23 | github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= 24 | github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= 25 | github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 26 | github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 27 | github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 28 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 29 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 30 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 31 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 32 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 33 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 34 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 35 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 36 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 37 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 38 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 39 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 40 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 41 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 42 | github.com/moov-io/base v0.53.0 h1:rpPWEbd/NTWApLzFq2AYbCZUlIv99OtvQcan7yArJVE= 43 | github.com/moov-io/base v0.53.0/go.mod h1:F2cdACBgJHNemPrOxvc88ezIqFL6ymErB4hOuPR+axg= 44 | github.com/moov-io/base v0.54.3 h1:icczGx5nYeUIpjvdjW86pulDecU/yM8NMrraM81gems= 45 | github.com/moov-io/base v0.54.3/go.mod h1:m1cuKJ5kYxQl97xfYU/pF4em+aMOuGA+TMHuQL17KYc= 46 | github.com/moov-io/base v0.54.4 h1:E5eThBonMp7qLLjyWcdMyF0GJZkUEqml/XUmVcYyO2A= 47 | github.com/moov-io/base v0.54.4/go.mod h1:Kv7dUpZ2eKKC2yrNQN/zbkeRmTH1CNojxpp31UmH4yo= 48 | github.com/moov-io/base v0.55.0 h1:eP0r9rQEcbhkNxm+MFrCVCONLrycHXbajo8Vf1PXEuY= 49 | github.com/moov-io/base v0.55.0/go.mod h1:1NCnzm42u3Ef1bWGYQ1YNhQzzoDLey8YfSwTtQoyWv0= 50 | github.com/moov-io/base v0.55.1 h1:bnbphcE7+E4wpK0CYOCuo6KbLKFWyqTGri1yHukMYeM= 51 | github.com/moov-io/base v0.55.1/go.mod h1:Aiq0LQFbE1ha+bAOGxDtnu8ZEGzZorcIK/1jALBPk/E= 52 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 53 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 54 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 55 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 56 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 57 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 58 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 59 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 60 | github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= 61 | github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 62 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 63 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 64 | github.com/rickar/cal/v2 v2.1.19 h1:rhWU/LGZnBwsEiJXMRAM9JooLH/8OHN8wWZ9qq13XJk= 65 | github.com/rickar/cal/v2 v2.1.19/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= 66 | github.com/rickar/cal/v2 v2.1.22 h1:P+Sf6E3vlgku9CbWusbwifVku+4oUVSFHZ3bGr1J61I= 67 | github.com/rickar/cal/v2 v2.1.22/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= 68 | github.com/rickar/cal/v2 v2.1.23 h1:uTYRCpZwMKIBIyNhjf+24ukdt3n8JegMZCst4om79FA= 69 | github.com/rickar/cal/v2 v2.1.23/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= 70 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 71 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 72 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 73 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 74 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 75 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= 76 | golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= 77 | golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 78 | golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= 79 | golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 80 | golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= 81 | golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 82 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 83 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 84 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 85 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 86 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 87 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 88 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 89 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 90 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 91 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 92 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 93 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 94 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 95 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 96 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 97 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 98 | golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= 99 | golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= 100 | golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= 101 | golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= 102 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 103 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 104 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 105 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 106 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 107 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 108 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 109 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 110 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 111 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | PLATFORM=$(shell uname -s | tr '[:upper:]' '[:lower:]') 2 | VERSION := $(shell grep -Eo '(v[0-9]+[\.][0-9]+[\.][0-9]+(-[a-zA-Z0-9]*)?)' version.go) 3 | 4 | .PHONY: build docker release check 5 | 6 | build: 7 | # main FED binary 8 | CGO_ENABLED=0 go build -o ./bin/server github.com/moov-io/fed/cmd/server 9 | # fedtest binary 10 | CGO_ENABLED=0 go build -o bin/fedtest ./cmd/fedtest 11 | 12 | .PHONY: check 13 | check: 14 | ifeq ($(OS),Windows_NT) 15 | @echo "Skipping checks on Windows, currently unsupported." 16 | else 17 | @wget -O lint-project.sh https://raw.githubusercontent.com/moov-io/infra/master/go/lint-project.sh 18 | @chmod +x ./lint-project.sh 19 | COVER_THRESHOLD=40.0 ./lint-project.sh 20 | endif 21 | 22 | .PHONY: client 23 | client: 24 | ifeq ($(OS),Windows_NT) 25 | @echo "Please generate client on macOS or Linux, currently unsupported on windows." 26 | else 27 | # Versions from https://github.com/OpenAPITools/openapi-generator/releases 28 | @chmod +x ./openapi-generator 29 | @rm -rf ./client 30 | OPENAPI_GENERATOR_VERSION=4.3.1 ./openapi-generator generate --git-user-id=moov-io --git-repo-id=fed --package-name client -i ./api/client.yaml -g go -o ./client 31 | rm -f ./client/go.mod ./client/go.sum ./client/.travis.yml ./client/git_push.sh 32 | go fmt ./... 33 | go build github.com/moov-io/fed/client 34 | go test ./client/... 35 | endif 36 | 37 | .PHONY: clean 38 | clean: 39 | ifeq ($(OS),Windows_NT) 40 | @echo "Skipping cleanup on Windows, currently unsupported." 41 | else 42 | @rm -rf ./bin/ cover.out coverage.txt openapi-generator-cli-*.jar misspell* staticcheck* lint-project.sh 43 | endif 44 | 45 | dist: clean client build 46 | ifeq ($(OS),Windows_NT) 47 | CGO_ENABLED=1 GOOS=windows go build -o bin/fed.exe github.com/moov-io/fed/cmd/server 48 | else 49 | CGO_ENABLED=1 GOOS=$(PLATFORM) go build -o bin/fed-$(PLATFORM)-amd64 github.com/moov-io/fed/cmd/server 50 | endif 51 | 52 | docker: clean 53 | # main FED image 54 | docker build --pull -t moov/fed:$(VERSION) -f Dockerfile . 55 | docker tag moov/fed:$(VERSION) moov/fed:latest 56 | # OpenShift Docker image 57 | docker build --pull -t quay.io/moov/fed:$(VERSION) -f Dockerfile-openshift --build-arg VERSION=$(VERSION) . 58 | docker tag quay.io/moov/fed:$(VERSION) quay.io/moov/fed:latest 59 | # fedtest image 60 | docker build --pull -t moov/fedtest:$(VERSION) -f Dockerfile-fedtest ./ 61 | docker tag moov/fedtest:$(VERSION) moov/fedtest:latest 62 | 63 | clean-integration: 64 | docker compose kill 65 | docker compose rm -v -f 66 | 67 | test-integration: clean-integration 68 | docker compose up -d 69 | sleep 5 70 | ./bin/fedtest -local 71 | 72 | release: docker AUTHORS 73 | go vet ./... 74 | go test -coverprofile=cover-$(VERSION).out ./... 75 | git tag -f $(VERSION) 76 | 77 | release-push: 78 | docker push moov/fed:$(VERSION) 79 | docker push moov/fed:latest 80 | 81 | quay-push: 82 | docker push quay.io/moov/fed:$(VERSION) 83 | docker push quay.io/moov/fed:latest 84 | 85 | .PHONY: cover-test cover-web 86 | cover-test: 87 | go test -coverprofile=cover.out ./... 88 | cover-web: 89 | go tool cover -html=cover.out 90 | 91 | # From https://github.com/genuinetools/img 92 | .PHONY: AUTHORS 93 | AUTHORS: 94 | @$(file >$@,# This file lists all individuals having contributed content to the repository.) 95 | @$(file >>$@,# For how it is generated, see `make AUTHORS`.) 96 | @echo "$(shell git log --format='\n%aN <%aE>' | LC_ALL=C.UTF-8 sort -uf)" >> $@ 97 | -------------------------------------------------------------------------------- /normalize.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package fed 6 | 7 | import ( 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | var ( 13 | symbolReplacer = strings.NewReplacer( 14 | ".", "", 15 | ",", "", 16 | ":", "", 17 | "/", " ", 18 | "(", "", 19 | ")", "", 20 | "-", " ", // 'CITIBANK-NEW YORK' => 'CITIBANK NEW YORK' 21 | ) 22 | wasteReplacer = strings.NewReplacer( 23 | "N.A.", " ", 24 | ) 25 | spaceTrimming = regexp.MustCompile(`\s{2,}`) 26 | 27 | // Replacements for banks that have lots of similar names. 28 | // The big banks will have '$name - Arizona' which confuses logo search tools, so just replace them with known-good names. 29 | // 30 | // Based on https://github.com/wealthsimple/frb-participants/blob/master/data/manually-normalized-institution-names.yml 31 | nameReplacements = map[string]string{ 32 | "ALLY BANK": "Ally Bank", 33 | "AMERICAN EXPRESS": "American Express", 34 | "BANK OF AMERICA": "Bank of America", 35 | "CAPITAL ONE": "Capital One", 36 | "CHARLES SCHWAB BANK": "Charles Schwab", 37 | "CITIBANK": "Citibank", 38 | "FIDELITY BANK": "Fidelity", 39 | "HSBC": "HSBC Bank", 40 | "JPMORGAN CHASE": "Chase", 41 | "PNC BANK": "PNC Bank", 42 | "SUNTRUST": "SunTrust", 43 | "TD BANK": "TD Bank", 44 | "WELLS FARGO": "Wells Fargo", 45 | "US BANK": "US Bank", 46 | "USAA": "USAA", 47 | } 48 | ) 49 | 50 | func Normalize(name string) string { 51 | for sub, answer := range nameReplacements { 52 | if strings.Contains(name, sub) { 53 | return answer 54 | } 55 | } 56 | return RemoveDuplicatedSpaces(StripSymbols(StripWaste(name))) 57 | } 58 | 59 | func StripSymbols(name string) string { 60 | return symbolReplacer.Replace(name) 61 | } 62 | 63 | func StripWaste(name string) string { 64 | return wasteReplacer.Replace(name) 65 | } 66 | 67 | func RemoveDuplicatedSpaces(name string) string { 68 | return spaceTrimming.ReplaceAllString(name, " ") 69 | } 70 | -------------------------------------------------------------------------------- /normalize_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package fed 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestNormalize(t *testing.T) { 14 | cases := []struct { 15 | input, expected string 16 | }{ 17 | {input: "ALLY BANK", expected: "Ally Bank"}, 18 | {input: "BANK OF AMERICA, N.A. - ARIZONA", expected: "Bank of America"}, 19 | {input: "CITIBANK FSB", expected: "Citibank"}, 20 | {input: "CITIBANK /FLORIDA/BR", expected: "Citibank"}, 21 | {input: "CITIBANK-NEW YORK STATE", expected: "Citibank"}, 22 | {input: "CITIBANK (SOUTH DAKOTA) NA", expected: "Citibank"}, 23 | {input: "PNC BANK INC. - BALTIMORE", expected: "PNC Bank"}, 24 | {input: "SUNTRUST BANK/ SKYLIGHT", expected: "SunTrust"}, 25 | {input: "TD BANK NA (PC)", expected: "TD Bank"}, 26 | {input: "WELLS FARGO BANK, N.A.", expected: "Wells Fargo"}, 27 | } 28 | for i := range cases { 29 | output := Normalize(cases[i].input) 30 | require.Equal(t, cases[i].expected, output) 31 | } 32 | } 33 | 34 | func TestStripSymbols(t *testing.T) { 35 | cases := []struct { 36 | input, expected string 37 | }{ 38 | {input: "ALLY BANK", expected: "ALLY BANK"}, 39 | {input: "BANK OF AMERICA, N.A. - ARIZONA", expected: "BANK OF AMERICA NA ARIZONA"}, 40 | {input: "CITIBANK FSB", expected: "CITIBANK FSB"}, 41 | {input: "CITIBANK /FLORIDA/BR", expected: "CITIBANK FLORIDA BR"}, 42 | {input: "CITIBANK-NEW YORK STATE", expected: "CITIBANK NEW YORK STATE"}, 43 | {input: "CITIBANK (SOUTH DAKOTA) NA", expected: "CITIBANK SOUTH DAKOTA NA"}, 44 | {input: "PNC BANK INC. - BALTIMORE", expected: "PNC BANK INC BALTIMORE"}, 45 | {input: "SUNTRUST BANK/ SKYLIGHT", expected: "SUNTRUST BANK SKYLIGHT"}, 46 | {input: "TD BANK NA (PC)", expected: "TD BANK NA PC"}, 47 | {input: "WELLS FARGO BANK, N.A.", expected: "WELLS FARGO BANK NA"}, 48 | } 49 | for i := range cases { 50 | output := StripSymbols(cases[i].input) 51 | require.Equal(t, cases[i].expected, output) 52 | } 53 | } 54 | 55 | func TestStripWaste(t *testing.T) { 56 | cases := []struct { 57 | input, expected string 58 | }{ 59 | {input: "BANK OF AMERICA, N.A. - ARIZONA", expected: "BANK OF AMERICA, - ARIZONA"}, 60 | {input: "WELLS FARGO BANK, N.A.", expected: "WELLS FARGO BANK, "}, 61 | } 62 | for i := range cases { 63 | output := StripWaste(cases[i].input) 64 | require.Equal(t, cases[i].expected, output) 65 | } 66 | } 67 | 68 | func TestRemoveDuplicatedSpaces(t *testing.T) { 69 | cases := []struct { 70 | input, expected string 71 | }{ 72 | {input: "ALLY BANK", expected: "ALLY BANK"}, 73 | {input: "BANK OF AMERICA NA ARIZONA", expected: "BANK OF AMERICA NA ARIZONA"}, 74 | {input: "CITIBANK FLORIDA BR", expected: "CITIBANK FLORIDA BR"}, 75 | {input: "PNC BANK INC BALTIMORE", expected: "PNC BANK INC BALTIMORE"}, 76 | {input: "PNC BANK INC BALTIMORE", expected: "PNC BANK INC BALTIMORE"}, 77 | } 78 | for i := range cases { 79 | output := RemoveDuplicatedSpaces(cases[i].input) 80 | require.Equal(t, cases[i].expected, output) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /openapi-generator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # From https://github.com/OpenAPITools/openapi-generator 4 | # 5 | #### 6 | # Save as openapi-generator-cli on your PATH. chmod u+x. Enjoy. 7 | # 8 | # This script will query github on every invocation to pull the latest released version 9 | # of openapi-generator. 10 | # 11 | # If you want repeatable executions, you can explicitly set a version via 12 | # OPENAPI_GENERATOR_VERSION 13 | # e.g. (in Bash) 14 | # export OPENAPI_GENERATOR_VERSION=3.1.0 15 | # openapi-generator-cli.sh 16 | # or 17 | # OPENAPI_GENERATOR_VERSION=3.1.0 openapi-generator-cli.sh 18 | # 19 | # This is also helpful, for example, if you want want to evaluate a SNAPSHOT version. 20 | # 21 | # NOTE: Jars are downloaded on demand from maven into the same directory as this script 22 | # for every 'latest' version pulled from github. Consider putting this under its own directory. 23 | #### 24 | set -o pipefail 25 | 26 | for cmd in {mvn,python,curl}; do 27 | if ! command -v ${cmd} > /dev/null; then 28 | >&2 echo "This script requires '${cmd}' to be installed." 29 | exit 1 30 | fi 31 | done 32 | 33 | function latest.tag { 34 | local uri="https://api.github.com/repos/${1}/tags" 35 | curl -s ${uri} | python -c "import sys, json; print json.load(sys.stdin)[0]['name'][1:]" 36 | } 37 | 38 | ghrepo=openapitools/openapi-generator 39 | groupid=org.openapitools 40 | artifactid=openapi-generator-cli 41 | ver=${OPENAPI_GENERATOR_VERSION:-$(latest.tag $ghrepo)} 42 | 43 | jar=${artifactid}-${ver}.jar 44 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 45 | 46 | if [ ! -f ${DIR}/${jar} ]; then 47 | repo="central::default::https://repo1.maven.org/maven2/" 48 | if [[ ${ver} =~ ^.*-SNAPSHOT$ ]]; then 49 | repo="central::default::https://oss.sonatype.org/content/repositories/snapshots" 50 | fi 51 | mvn org.apache.maven.plugins:maven-dependency-plugin:2.9:get \ 52 | -DremoteRepositories=${repo} \ 53 | -Dartifact=${groupid}:${artifactid}:${ver} \ 54 | -Dtransitive=false \ 55 | -Ddest=${DIR}/${jar} 56 | fi 57 | 58 | java -ea \ 59 | ${JAVA_OPTS} \ 60 | -Xms512M \ 61 | -Xmx1024M \ 62 | -server \ 63 | -jar ${DIR}/${jar} "$@" -------------------------------------------------------------------------------- /openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | description: FED API is designed to create FEDACH and FEDWIRE dictionaries. The FEDACH dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive ACH entries. The FEDWIRE dictionary contains receiving depository financial institutions (RDFI’s) which are qualified to receive WIRE entries. This project implements a modern REST HTTP API for FEDACH Dictionary and FEDWIRE Dictionary. 4 | version: v1 5 | title: FED API 6 | contact: 7 | name: FED API Support 8 | url: 'https://github.com/moov-io/fed' 9 | license: 10 | name: Apache 2.0 11 | url: 'http://www.apache.org/licenses/LICENSE-2.0.html' 12 | servers: 13 | - url: 'http://localhost:8086' 14 | description: Local development 15 | tags: 16 | - name: FED 17 | description: FEDACH Dictionary and FEDWIRE Dictionary 18 | paths: 19 | /ping: 20 | get: 21 | tags: 22 | - FED 23 | summary: Ping the FED service to check if running 24 | operationId: ping 25 | responses: 26 | '200': 27 | description: Service is running properly 28 | content: 29 | text/plain: 30 | example: PONG 31 | /fed/ach/search: 32 | get: 33 | tags: 34 | - FED 35 | summary: Search FEDACH names and metadata 36 | operationId: searchFEDACH 37 | parameters: 38 | - name: X-Request-ID 39 | in: header 40 | description: Optional Request ID allows application developer to trace requests through the systems logs 41 | example: rs4f9915 42 | schema: 43 | type: string 44 | - name: X-User-ID 45 | in: header 46 | description: Optional User ID used to perform this search 47 | schema: 48 | type: string 49 | - name: name 50 | in: query 51 | schema: 52 | type: string 53 | example: Farmers 54 | description: FEDACH Financial Institution Name 55 | - name: routingNumber 56 | in: query 57 | schema: 58 | type: string 59 | example: 044112187 60 | description: FEDACH Routing Number for a Financial Institution 61 | - name: state 62 | in: query 63 | schema: 64 | type: string 65 | example: OH 66 | description: FEDACH Financial Institution State 67 | - name: city 68 | in: query 69 | schema: 70 | type: string 71 | example: CALDWELL 72 | description: FEDACH Financial Institution City 73 | - name: postalCode 74 | in: query 75 | schema: 76 | type: string 77 | example: 43724 78 | description: FEDACH Financial Institution Postal Code 79 | - name: limit 80 | in: query 81 | schema: 82 | type: integer 83 | example: 499 84 | description: Maximum results returned by a search 85 | responses: 86 | '200': 87 | description: FEDACH Participants returned from a search 88 | content: 89 | application/json: 90 | schema: 91 | $ref: '#/components/schemas/ACHDictionary' 92 | '400': 93 | description: Invalid, check error(s). 94 | content: 95 | application/json: 96 | schema: 97 | $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' 98 | '500': 99 | description: Internal error, check error(s) and report the issue. 100 | /fed/wire/search: 101 | get: 102 | tags: 103 | - FED 104 | summary: Search FEDWIRE names and metadata 105 | operationId: searchFEDWIRE 106 | parameters: 107 | - name: X-Request-ID 108 | in: header 109 | description: Optional Request ID allows application developer to trace requests through the systems logs 110 | example: rs4f9915 111 | schema: 112 | type: string 113 | - name: X-User-ID 114 | in: header 115 | description: Optional User ID used to perform this search 116 | schema: 117 | type: string 118 | - name: name 119 | in: query 120 | schema: 121 | type: string 122 | example: MIDWEST 123 | description: FEDWIRE Financial Institution Name 124 | - name: routingNumber 125 | in: query 126 | schema: 127 | type: string 128 | example: 091905114 129 | description: FEDWIRE Routing Number for a Financial Institution 130 | - name: state 131 | in: query 132 | schema: 133 | type: string 134 | example: IA 135 | description: FEDWIRE Financial Institution State 136 | - name: city 137 | in: query 138 | schema: 139 | type: string 140 | example: IOWA CITY 141 | description: FEDWIRE Financial Institution City 142 | - name: limit 143 | in: query 144 | schema: 145 | type: integer 146 | example: 499 147 | description: Maximum results returned by a search 148 | responses: 149 | '200': 150 | description: FEDWIRE Participants returned from a search 151 | content: 152 | application/json: 153 | schema: 154 | $ref: '#/components/schemas/WIREDictionary' 155 | '400': 156 | description: Invalid, check error(s). 157 | content: 158 | application/json: 159 | schema: 160 | $ref: 'https://raw.githubusercontent.com/moov-io/base/master/api/common.yaml#/components/schemas/Error' 161 | '500': 162 | description: Internal error, check error(s) and report the issue. 163 | 164 | components: 165 | schemas: 166 | ACHDictionary: 167 | description: Search results containing ACHDictionary of Participants 168 | properties: 169 | achParticipants: 170 | type: array 171 | items: 172 | $ref: '#/components/schemas/ACHParticipant' 173 | stats: 174 | $ref: '#/components/schemas/ListStats' 175 | ACHParticipant: 176 | description: ACHParticipant holds a FedACH dir routing record as defined by Fed ACH Format. https://www.frbservices.org/EPaymentsDirectory/achFormat.html 177 | properties: 178 | routingNumber: 179 | type: string 180 | minLength: 9 181 | maxLength: 9 182 | description: The institution's routing number 183 | example: '044112187' 184 | officeCode: 185 | type: string 186 | minLength: 1 187 | maxLength: 1 188 | description: | 189 | Main/Head Office or Branch 190 | 191 | * `O` - Main 192 | * `B` - Branch 193 | enum: 194 | - O 195 | - B 196 | example: 'O' 197 | servicingFRBNumber: 198 | type: string 199 | minLength: 9 200 | maxLength: 9 201 | description: Servicing Fed's main office routing number 202 | example: '041000014' 203 | recordTypeCode: 204 | type: string 205 | minLength: 1 206 | maxLength: 1 207 | description: | 208 | The code indicating the ABA number to be used to route or send ACH items to the RDFI 209 | 210 | * `0` - Institution is a Federal Reserve Bank 211 | * `1` - Send items to customer routing number 212 | * `2` - Send items to customer using new routing number field 213 | enum: 214 | - 0 215 | - 1 216 | - 2 217 | example: '1' 218 | revised: 219 | type: string 220 | maxLength: 8 221 | description: | 222 | Date of last revision 223 | 224 | * YYYYMMDD 225 | * Blank 226 | example: '20190311' 227 | newRoutingNumber: 228 | type: string 229 | minLength: 9 230 | maxLength: 9 231 | description: Financial Institution's new routing number resulting from a merger or renumber 232 | example: '000000000' 233 | customerName: 234 | type: string 235 | maxLength: 36 236 | description: Financial Institution Name 237 | example: FARMERS & MERCHANTS BANK 238 | achLocation: 239 | $ref: '#/components/schemas/ACHLocation' 240 | phoneNumber: 241 | type: string 242 | minLength: 10 243 | maxLength: 10 244 | description: The Financial Institution's phone number 245 | example: '7407325621' 246 | statusCode: 247 | type: string 248 | minLength: 1 249 | maxLength: 1 250 | description: | 251 | Code is based on the customers receiver code 252 | 253 | * `1` - Receives Gov/Comm 254 | enum: 255 | - 1 256 | example: '1' 257 | viewCode: 258 | type: string 259 | minLength: 1 260 | maxLength: 1 261 | description: |- 262 | Code is current view 263 | 264 | * `1` - Current view 265 | enum: 266 | - 1 267 | example: '1' 268 | ACHLocation: 269 | description: ACHLocation is the FEDACH delivery address 270 | properties: 271 | address: 272 | type: string 273 | maxLength: 36 274 | description: Street Address 275 | example: '430 NORTH ST' 276 | city: 277 | type: string 278 | maxLength: 20 279 | description: City 280 | example: 'CALDWELL' 281 | state: 282 | type: string 283 | minLength: 2 284 | maxLength: 2 285 | description: State 286 | example: 'OH' 287 | postalCode: 288 | type: string 289 | minLength: 5 290 | maxLength: 5 291 | description: Postal Code 292 | example: '43724' 293 | postalExtension: 294 | type: string 295 | minLength: 4 296 | maxLength: 4 297 | description: Postal Code Extension 298 | example: '0000' 299 | 300 | ListStats: 301 | type: object 302 | properties: 303 | records: 304 | type: integer 305 | example: 17124 306 | latest: 307 | type: string 308 | format: date-time 309 | 310 | WIREDictionary: 311 | description: Search results containing WIREDictionary of Participants 312 | properties: 313 | wireParticipants: 314 | type: array 315 | items: 316 | $ref: '#/components/schemas/WIREParticipant' 317 | stats: 318 | $ref: '#/components/schemas/ListStats' 319 | WIREParticipant: 320 | description: WIREParticipant holds a FedWIRE dir routing record as defined by Fed WIRE Format. https://frbservices.org/EPaymentsDirectory/fedwireFormat.html 321 | properties: 322 | routingNumber: 323 | type: string 324 | minLength: 9 325 | maxLength: 9 326 | description: The institution's routing number 327 | example: '091905114' 328 | telegraphicName: 329 | type: string 330 | maxLength: 18 331 | description: Short name of financial institution 332 | example: 'MIDWESTONE B&T' 333 | customerName: 334 | type: string 335 | maxLength: 36 336 | description: Financial Institution Name 337 | example: 'MIDWESTONE BK' 338 | wireLocation: 339 | $ref: '#/components/schemas/WIRELocation' 340 | fundsTransferStatus: 341 | type: string 342 | minLength: 1 343 | maxLength: 1 344 | description: | 345 | Designates funds transfer status 346 | 347 | * `Y` - Eligible 348 | * `N` - Ineligible 349 | enum: 350 | - Y 351 | - N 352 | example: 'Y' 353 | fundsSettlementOnlyStatus: 354 | type: string 355 | maxLength: 1 356 | description: | 357 | Designates funds settlement only status 358 | 359 | * `S` - Settlement-Only 360 | enum: 361 | - S 362 | example: '' 363 | bookEntrySecuritiesTransferStatus: 364 | type: string 365 | minLength: 1 366 | maxLength: 1 367 | description: | 368 | Designates book entry securities transfer status 369 | 370 | * `Y` - Eligible 371 | * `N` - Ineligible 372 | enum: 373 | - Y 374 | - N 375 | example: 'N' 376 | date: 377 | type: string 378 | maxLength: 8 379 | description: | 380 | Date of last revision 381 | 382 | * YYYYMMDD 383 | * Blank 384 | example: '20190401' 385 | WIRELocation: 386 | description: WIRELocation is the FEDWIRE delivery address 387 | properties: 388 | city: 389 | type: string 390 | maxLength: 25 391 | description: City 392 | example: 'IOWA CITY' 393 | state: 394 | type: string 395 | minLength: 2 396 | maxLength: 2 397 | description: State 398 | example: 'IA' 399 | -------------------------------------------------------------------------------- /pkg/download/download.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package download 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "net/http" 13 | "net/url" 14 | "os" 15 | "time" 16 | ) 17 | 18 | const DefaultFRBDownloadURLTemplate = "https://frbservices.org/EPaymentsDirectory/directories/%s?format=json" 19 | 20 | var ( 21 | ErrMissingConfigValue = errors.New("missing config value") 22 | ErrMissingRoutingNumber = errors.New("missing routing number") 23 | ErrMissingDownloadCD = errors.New("missing download code") 24 | ) 25 | 26 | type Client struct { 27 | httpClient *http.Client 28 | 29 | routingNumber string // X_FRB_EPAYMENTS_DIRECTORY_ORG_ID header 30 | downloadCode string // X_FRB_EPAYMENTS_DIRECTORY_DOWNLOAD_CD 31 | downloadURL string // defaults to "https://frbservices.org/EPaymentsDirectory/directories/%s?format=json" where %s is the list name 32 | 33 | } 34 | 35 | type ClientOpts struct { 36 | HTTPClient *http.Client 37 | RoutingNumber, DownloadCode, DownloadURL string 38 | } 39 | 40 | func NewClient(opts *ClientOpts) (*Client, error) { 41 | if opts == nil { 42 | opts = &ClientOpts{} 43 | } 44 | if opts.HTTPClient == nil { 45 | opts.HTTPClient = &http.Client{ 46 | // These files can be fairly large to buffer to us and the FRB 47 | // services can be slow, so we default to a hefty timeout. 48 | Timeout: 90 * time.Second, 49 | } 50 | } 51 | 52 | routingNum, rnExists := os.LookupEnv("FRB_ROUTING_NUMBER") 53 | downloadcd, dcdExists := os.LookupEnv("FRB_DOWNLOAD_CODE") 54 | downloadurltemp, urlExists := os.LookupEnv("FRB_DOWNLOAD_URL_TEMPLATE") 55 | 56 | if !urlExists || downloadurltemp == "" { 57 | if !rnExists || routingNum == "" { 58 | return nil, fmt.Errorf("%w: %w", ErrMissingConfigValue, ErrMissingRoutingNumber) 59 | } 60 | 61 | if !dcdExists || downloadcd == "" { 62 | return nil, fmt.Errorf("%w: %w", ErrMissingConfigValue, ErrMissingDownloadCD) 63 | } 64 | 65 | downloadurltemp = DefaultFRBDownloadURLTemplate 66 | } 67 | 68 | return &Client{ 69 | httpClient: opts.HTTPClient, 70 | routingNumber: routingNum, 71 | downloadCode: downloadcd, 72 | downloadURL: downloadurltemp, 73 | }, nil 74 | } 75 | 76 | // GetList downloads an FRB list and saves it into an io.Reader. 77 | // Example listName values: fedach, fedwire 78 | func (c *Client) GetList(listName string) (io.Reader, error) { 79 | where, err := url.Parse(fmt.Sprintf(c.downloadURL, listName)) 80 | if err != nil { 81 | return nil, fmt.Errorf("url: %v", err) 82 | } 83 | 84 | req, err := http.NewRequest("GET", where.String(), nil) 85 | if err != nil { 86 | return nil, fmt.Errorf("building %s url: %v", listName, err) 87 | } 88 | 89 | if c.downloadCode != "" && c.routingNumber != "" { 90 | req.Header.Set("X_FRB_EPAYMENTS_DIRECTORY_ORG_ID", c.routingNumber) 91 | req.Header.Set("X_FRB_EPAYMENTS_DIRECTORY_DOWNLOAD_CD", c.downloadCode) 92 | } 93 | 94 | // perform our request 95 | resp, err := c.httpClient.Do(req) 96 | if err != nil { 97 | return nil, fmt.Errorf("http get: %v", err) 98 | } 99 | if resp != nil && resp.Body != nil { 100 | defer resp.Body.Close() 101 | } 102 | 103 | // Quit if we fail to download 104 | if resp.StatusCode >= 299 { 105 | return nil, fmt.Errorf("unexpected http status: %d", resp.StatusCode) 106 | } 107 | 108 | var out bytes.Buffer 109 | if n, err := io.Copy(&out, resp.Body); n == 0 || err != nil { 110 | return nil, fmt.Errorf("copying n=%d: %v", n, err) 111 | } 112 | if out.Len() > 0 { 113 | return &out, nil 114 | } 115 | return nil, nil 116 | } 117 | -------------------------------------------------------------------------------- /pkg/download/download_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package download 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "net/http/httptest" 13 | "os" 14 | "path/filepath" 15 | "testing" 16 | ) 17 | 18 | func TestClient__fedach(t *testing.T) { 19 | client := setupClient(t) 20 | 21 | fedach, err := client.GetList("fedach") 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | bs, _ := io.ReadAll(io.LimitReader(fedach, 10024)) 27 | if !bytes.Contains(bs, []byte("fedACHParticipants")) { 28 | t.Errorf("unexpected output:\n%s", string(bs)) 29 | } 30 | } 31 | 32 | func TestClient__fedach_custom_url(t *testing.T) { 33 | file, err := os.ReadFile(filepath.Join("..", "..", "data", "fedachdir.json")) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | 38 | mockHTTPServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 39 | fmt.Fprint(writer, string(file)) 40 | })) 41 | defer mockHTTPServer.Close() 42 | 43 | t.Setenv("FRB_DOWNLOAD_URL_TEMPLATE", mockHTTPServer.URL+"/%s") 44 | t.Setenv("FRB_ROUTING_NUMBER", "123456789") 45 | t.Setenv("FRB_DOWNLOAD_CODE", "a1b2c3d4-123b-9876-1234-z1x2y3a1b2c3") 46 | 47 | client := setupClient(t) 48 | 49 | fedach, err := client.GetList("fedach") 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | bs, _ := io.ReadAll(io.LimitReader(fedach, 10024)) 55 | if !bytes.Equal(bs, file) { 56 | t.Errorf("unexpected output:\n%s", string(bs)) 57 | } 58 | } 59 | 60 | func TestClient__fedwire(t *testing.T) { 61 | client := setupClient(t) 62 | 63 | fedwire, err := client.GetList("fedwire") 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | bs, _ := io.ReadAll(io.LimitReader(fedwire, 10024)) 69 | if !bytes.Contains(bs, []byte("fedwireParticipants")) { 70 | t.Errorf("unexpected output:\n%s", string(bs)) 71 | } 72 | } 73 | 74 | func TestClient__wire_custom_url(t *testing.T) { 75 | file, err := os.ReadFile(filepath.Join("..", "..", "data", "fpddir.json")) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | mockHTTPServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { 80 | if request.URL.Path != "/fedwire" { 81 | writer.WriteHeader(http.StatusNotFound) 82 | } 83 | fmt.Fprint(writer, string(file)) 84 | })) 85 | defer mockHTTPServer.Close() 86 | 87 | t.Setenv("FRB_DOWNLOAD_URL_TEMPLATE", mockHTTPServer.URL+"/%s") 88 | t.Setenv("FRB_ROUTING_NUMBER", "123456789") 89 | t.Setenv("FRB_DOWNLOAD_CODE", "a1b2c3d4-123b-9876-1234-z1x2y3a1b2c3") 90 | 91 | client := setupClient(t) 92 | 93 | fedwire, err := client.GetList("fedwire") 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | bs, _ := io.ReadAll(io.LimitReader(fedwire, 10024)) 99 | if !bytes.Equal(bs, file) { 100 | t.Errorf("unexpected output:\n%s", string(bs)) 101 | } 102 | } 103 | 104 | func setupClient(t *testing.T) *Client { 105 | t.Helper() 106 | 107 | routingNumber := os.Getenv("FRB_ROUTING_NUMBER") 108 | downloadCode := os.Getenv("FRB_DOWNLOAD_CODE") 109 | downloadURL := os.Getenv("FRB_DOWNLOAD_URL_TEMPLATE") 110 | if routingNumber == "" || downloadCode == "" { 111 | t.Skip("missing FRB routing number or download code") 112 | } 113 | 114 | client, err := NewClient(&ClientOpts{ 115 | RoutingNumber: routingNumber, 116 | DownloadCode: downloadCode, 117 | DownloadURL: downloadURL, 118 | }) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | return client 123 | } 124 | -------------------------------------------------------------------------------- /pkg/strcmp/strcmp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package strcomp defines functions to return a percent match between two 6 | // strings. Many algorithms are available, but all are normalized to [0.00, 1.00] 7 | // for use as percentages. 8 | // 9 | // Any value outside of the normalized range represents a bug and should be fixed. 10 | 11 | // ToDo: Move to internal directory 12 | 13 | package strcmp 14 | 15 | import ( 16 | "unicode/utf8" 17 | 18 | "github.com/xrash/smetrics" 19 | ) 20 | 21 | // JaroWinkler is a more accurate version of the Jaro algorithm. It works by boosting the 22 | // score of exact matches at the beginning of the strings. By doing this, Winkler says that 23 | // typos are less common to happen at the beginning. 24 | // 25 | // For this to happen, it introduces two more parameters: the boostThreshold and the prefixSize. 26 | // These are commonly set to 0.7 and 4, respectively. 27 | // 28 | // From: https://godoc.org/github.com/xrash/smetrics 29 | func JaroWinkler(a, b string) float64 { 30 | if a == "" || b == "" { 31 | return 0.00 32 | } 33 | return smetrics.JaroWinkler(a, b, 0.7, 4) 34 | } 35 | 36 | // Levenshtein is the "edit distance" between two strings. This is the count of operations 37 | // (insert, delete, replace) needed for two strings to be equal. 38 | func Levenshtein(a, b string) float64 { 39 | if a == "" || b == "" { 40 | return 0.00 41 | } 42 | 43 | length := utf8.RuneCountInString(a) 44 | if n := utf8.RuneCountInString(b); n > length { 45 | length = n // set length to larger value of the two strings 46 | } 47 | ed := float64(smetrics.WagnerFischer(a, b, 1, 1, 2)) 48 | 49 | score := ed / float64(length) 50 | if score > 1.00 { 51 | // If more edits are required than the string's length a and b aren't equal. 52 | return 0.00 53 | } 54 | return 1 - score 55 | } 56 | 57 | // Soundex is a phonetic algorithm that considers how the words sound in english. 58 | // Soundex maps a name to a 4-byte string consisting of the first letter of the original string and three numbers. Strings that sound similar should map to the same thing. 59 | // 60 | // Retruned is Hamming computed over both phonetic outputs. 61 | func Soundex(a, b string) float64 { 62 | if a == "" || b == "" { 63 | return 0.00 64 | } 65 | return hamming(smetrics.Soundex(a), smetrics.Soundex(b)) 66 | } 67 | 68 | // Hamming distance is the minimum number of substitutions required to change one string into the other. 69 | func hamming(a, b string) float64 { 70 | if a == "" || b == "" { 71 | return 0.00 72 | } 73 | 74 | length := utf8.RuneCountInString(a) 75 | if n := utf8.RuneCountInString(b); n != length { 76 | return 0.0 77 | } 78 | n, _ := smetrics.Hamming(a, b) 79 | return 1 - (float64(n) / float64(length)) 80 | } 81 | -------------------------------------------------------------------------------- /pkg/strcmp/strcmp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package strcmp 6 | 7 | import ( 8 | "crypto/rand" 9 | "encoding/hex" 10 | "flag" 11 | "math" 12 | mrand "math/rand" 13 | "strings" 14 | "testing" 15 | 16 | "github.com/docker/docker/pkg/namesgenerator" 17 | ) 18 | 19 | var ( 20 | flagIterations = flag.Int("iterations", 1000, "How many iterations of each algorithm to test") 21 | ) 22 | 23 | func randString() string { 24 | size := mrand.Uint32() % 1000 //nolint:gosec // max string size of 1k 25 | bs := make([]byte, size) 26 | n, err := rand.Read(bs) 27 | if err != nil || n == 0 { 28 | return "" 29 | } 30 | return strings.ToLower(hex.EncodeToString(bs)) 31 | } 32 | 33 | func TestJaroWinkler(t *testing.T) { 34 | for i := 0; i < *flagIterations; i += 1 { 35 | a, b := randString(), randString() 36 | check(t, a, b, JaroWinkler(a, b)) 37 | } 38 | } 39 | 40 | func TestLevenshtein(t *testing.T) { 41 | for i := 0; i < *flagIterations; i += 1 { 42 | a, b := randString(), randString() 43 | check(t, a, b, Levenshtein(a, b)) 44 | } 45 | } 46 | 47 | func TestHamming(t *testing.T) { 48 | for i := 0; i < *flagIterations; i += 1 { 49 | a, b := randString(), randString() 50 | check(t, a, b, hamming(a, b)) 51 | } 52 | } 53 | 54 | func TestSoundex(t *testing.T) { 55 | for i := 0; i < 500; i += 1 { 56 | parts := strings.Split(namesgenerator.GetRandomName(0), "_") 57 | if len(parts) < 2 { 58 | continue // invalid random name 59 | } 60 | 61 | a, b := parts[0], parts[1] 62 | score := Soundex(a, b) 63 | if score > 1.0 || score < 0.0 { 64 | t.Fatalf("a=%q b=%q got score %.2f", a, b, score) 65 | } 66 | 67 | score = Soundex(a, a) 68 | if !eql(score, 1.0) { 69 | t.Fatalf("a=%q b=%q got score: %.2f", a, a, score) 70 | } 71 | } 72 | 73 | type test struct { 74 | a, b string 75 | score float64 76 | } 77 | 78 | // Static tests 79 | cases := []test{ 80 | {"Adam", "Bob", 0.25}, 81 | {"Euler", "Ellery", 1.0}, 82 | {"Lloyd", "Ladd", 1.0}, 83 | } 84 | for i := range cases { 85 | score := Soundex(cases[i].a, cases[i].b) 86 | if !eql(score, cases[i].score) { 87 | t.Fatalf("a=%q b=%q got score: %.2f and expected: %.2f", cases[i].a, cases[i].b, score, cases[i].score) 88 | } 89 | } 90 | } 91 | 92 | func one(n float64) bool { 93 | return eql(n, 1.0) 94 | } 95 | 96 | func zero(n float64) bool { 97 | return eql(n, 0.0) 98 | } 99 | 100 | func eql(a, b float64) bool { 101 | return math.Abs(a-b) < 0.001 102 | } 103 | 104 | func check(t *testing.T, a, b string, score float64) { 105 | t.Helper() 106 | 107 | if one(score) && a != b { 108 | t.Fatalf("a=%q b=%q matched", a, b) 109 | } 110 | if zero(score) && a == b { 111 | t.Fatalf("a=%q b=%q didn't match", a, b) 112 | } 113 | if score > 1.0 || score < 0.0 { 114 | t.Fatalf("a=%q b=%q got score %.2f", a, b, score) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "prConcurrentLimit": 0, 4 | "prHourlyLimit": 0, 5 | "automerge": true, 6 | "automergeType": "pr", 7 | "packageRules": [ 8 | { 9 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 10 | "automerge": true 11 | }, 12 | { 13 | "matchUpdateTypes": ["major"], 14 | "automerge": false 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /validators.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package fed 6 | 7 | import ( 8 | "errors" 9 | "regexp" 10 | ) 11 | 12 | var ( 13 | numericRegex = regexp.MustCompile(`[^0-9]`) 14 | msgNumeric = "is not 0-9" 15 | ) 16 | 17 | // validator is common validation and formatting of golang types to fed type strings 18 | type validator struct{} 19 | 20 | // isNumeric checks if a string only contains ASCII numeric (0-9) characters 21 | func (v *validator) isNumeric(s string) error { 22 | if numericRegex.MatchString(s) { 23 | // [^0-9] 24 | return errors.New(msgNumeric) 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Moov Authors 2 | // Use of this source code is governed by an Apache License 3 | // license that can be found in the LICENSE file. 4 | 5 | package fed 6 | 7 | // Version is the current version 8 | const Version = "v0.13.0" 9 | --------------------------------------------------------------------------------